mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 07:04:57 +01:00
Merge pull request #563 from Cyberboss/Chatrevisionconfig
Iiiiii've been working on the railllllroooooadddd
This commit is contained in:
+15
-5
@@ -9,7 +9,7 @@ The TGS4 API is designed to be a fully realized RESTful service. Once hosted, fo
|
||||
|
||||
Routes and their usages are defined as follows
|
||||
|
||||
`[I (If Instance is required)] <Http Method> "<Route>" [Request Model] => <Response Model>`
|
||||
[I (If Instance is required)] <`Http Method`> "<`Route`>" [Request Model] => <`Response Model`>
|
||||
|
||||
@section api_lib Official Libraries
|
||||
|
||||
@@ -29,7 +29,7 @@ This document will reference the canonical C# models in the @ref Tgstation.Serve
|
||||
|
||||
@section api_header Headers
|
||||
|
||||
TGS4 expects this set of headers. Failure to provide them may result in
|
||||
TGS4 expects this set of headers. Failure to provide them may result in 400 error responses
|
||||
|
||||
- User-Agent: The user agent product header value of the calling program. Should be in the form Agent/Version (i.e. SomeTgsClient/1.2.4)
|
||||
- Accept: application/json
|
||||
@@ -261,7 +261,7 @@ To clone the repository if it doesn't yet exist use the following request:
|
||||
|
||||
I PUT "/Repository" => @ref Tgstation.Server.Api.Models.Repository
|
||||
|
||||
The clone job will be represented by the @ref Tgstation.Server.Api.Models.Repository.ActiveJob field. Specify the @ref Tgstation.Server.Api.Models.Repository.Origin URL. Optionally specify the initial @ref Tgstation.Server.Api.Models.Repository.Reference as a git tag or branch. Be sure to specify the authentication fields if necessary to access your repository
|
||||
The clone job will be represented by the @ref Tgstation.Server.Api.Models.Repository.ActiveJob field. Specify the @ref Tgstation.Server.Api.Models.Repository.Origin URL. Optionally specify the initial @ref Tgstation.Server.Api.Models.Repository.Reference as a git tag or branch. Be sure to specify the authentication fields if necessary to access your repository. Will return 409 if the repository already exists or is already being cloned
|
||||
|
||||
To delete an existing repository make the following request:
|
||||
|
||||
@@ -284,14 +284,14 @@ git pull (Only if @ref Tgstation.Server.Api.Models.Repository.Reference is set):
|
||||
}
|
||||
@endcode
|
||||
|
||||
git checkout <commit sha> (Unsets @ref Tgstation.Server.Api.Models.Repository.Reference):
|
||||
git checkout <`commit sha`> (Unsets @ref Tgstation.Server.Api.Models.Repository.Reference):
|
||||
@code{.json}
|
||||
{
|
||||
"checkoutSha": "<commit sha>"
|
||||
}
|
||||
@endcode
|
||||
|
||||
git checkout -f <branch or tag> && git clean -fxd (Sets @ref Tgstation.Server.Api.Models.Repository.Reference):
|
||||
git checkout -f <`branch or tag`> && git clean -fxd (Sets @ref Tgstation.Server.Api.Models.Repository.Reference):
|
||||
@code{.json}
|
||||
{
|
||||
"reference": "<branch or tag>"
|
||||
@@ -364,4 +364,14 @@ The job object returned represents the compile job
|
||||
{}
|
||||
@endcode
|
||||
|
||||
The compiler endpoint is also used to read compile jobs
|
||||
|
||||
To list successful compile jobs ids use:
|
||||
|
||||
I GET "/DreamMaker/List" => Array of @ref Tgstation.Server.Api.Models.CompileJob
|
||||
|
||||
To get a specific compile job use:
|
||||
|
||||
I GET "/DreamMaker/{CompileJobId}" => @ref Tgstation.Server.Api.Models.CompileJob
|
||||
|
||||
*/
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Tgstation.Server.Api
|
||||
/// <summary>
|
||||
/// The current <see cref="AssemblyName"/>
|
||||
/// </summary>
|
||||
static readonly AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName();
|
||||
internal static readonly AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.Instance.Id"/> being accessed
|
||||
@@ -85,6 +85,17 @@ namespace Tgstation.Server.Api
|
||||
/// </summary>
|
||||
public bool IsTokenAuthentication => Token != null;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given <paramref name="otherVersion"/> is compatible with our own
|
||||
/// </summary>
|
||||
/// <param name="otherVersion">The <see cref="Version"/> to test</param>
|
||||
/// <returns><see langword="true"/> if the given version is compatible with the API. <see langword="false"/> otherwise</returns>
|
||||
public static bool CheckCompatibility(Version otherVersion)
|
||||
{
|
||||
var ourVersion = assemblyName.Version;
|
||||
return !(ourVersion.Major != otherVersion.Major || ourVersion.Minor != otherVersion.Minor || ourVersion.Build > otherVersion.Build);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct <see cref="ApiHeaders"/> for JWT authentication
|
||||
/// </summary>
|
||||
@@ -141,10 +152,8 @@ namespace Tgstation.Server.Api
|
||||
ApiVersion = apiVersion;
|
||||
UserAgent = clientUserAgent.Product;
|
||||
|
||||
//check api version compatibility
|
||||
var ourVersion = assemblyName.Version;
|
||||
if (ourVersion.Major != ApiVersion.Major || ourVersion.Minor != ApiVersion.Minor || ourVersion.Build > ApiVersion.Build)
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Given API version is incompatible with version {0}!", ourVersion));
|
||||
if(!CheckCompatibility(ApiVersion))
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Given API version is incompatible with version {0}!", ApiVersion));
|
||||
|
||||
if (!requestHeaders.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorization))
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} header!", HeaderNames.Authorization));
|
||||
@@ -207,10 +216,13 @@ namespace Tgstation.Server.Api
|
||||
/// Set <see cref="HttpRequestHeaders"/> using the <see cref="ApiHeaders"/>. This initially clears <paramref name="headers"/>
|
||||
/// </summary>
|
||||
/// <param name="headers">The <see cref="HttpRequestHeaders"/> to set</param>
|
||||
public void SetRequestHeaders(HttpRequestHeaders headers)
|
||||
/// <param name="instanceId">The <see cref="Models.Instance.Id"/> for the request</param>
|
||||
public void SetRequestHeaders(HttpRequestHeaders headers, long? instanceId = null)
|
||||
{
|
||||
if (headers == null)
|
||||
throw new ArgumentNullException(nameof(headers));
|
||||
if (instanceId.HasValue && InstanceId.HasValue && instanceId != InstanceId)
|
||||
throw new InvalidOperationException("Specified instance ID in constructor and SetRequestHeaders!");
|
||||
|
||||
headers.Clear();
|
||||
headers.Accept.Add(new MediaTypeWithQualityHeaderValue(ApplicationJson));
|
||||
@@ -223,8 +235,9 @@ namespace Tgstation.Server.Api
|
||||
}
|
||||
headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent));
|
||||
headers.Add(ApiVersionHeader, ApiVersion.ToString());
|
||||
if(InstanceId.HasValue)
|
||||
headers.Add(instanceIdHeader, InstanceId.ToString());
|
||||
instanceId = instanceId ?? InstanceId;
|
||||
if (instanceId.HasValue)
|
||||
headers.Add(instanceIdHeader, instanceId.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -6,16 +6,16 @@ using Tgstation.Server.Api.Rights;
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class ChatSettings : Internal.ChatSettings
|
||||
public sealed class ChatBot : Internal.ChatBot
|
||||
{
|
||||
/// <summary>
|
||||
/// Channels the Discord bot should listen/announce in
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = ChatSettingsRights.WriteChannels)]
|
||||
[Permissions(WriteRight = ChatBotRights.WriteChannels)]
|
||||
public List<ChatChannel> Channels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Validates <see cref="Channels"/> are correct for the <see cref="Internal.ChatSettings.Provider"/>
|
||||
/// Validates <see cref="Channels"/> are correct for the <see cref="Internal.ChatBot.Provider"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool ValidateProviderChannelTypes()
|
||||
@@ -1,5 +1,4 @@
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Tgstation.Server.Api.Models
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an error message returned by the server
|
||||
@@ -9,5 +11,10 @@
|
||||
/// A human readable description of the error
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The version of the API the server is using
|
||||
/// </summary>
|
||||
public Version SeverApiVersion { get; set; } = ApiHeaders.assemblyName.Version;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,10 +47,10 @@ namespace Tgstation.Server.Api.Models
|
||||
public RepositoryRights? RepositoryRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.ChatSettingsRights"/> of the <see cref="InstanceUser"/>
|
||||
/// The <see cref="Rights.ChatBotRights"/> of the <see cref="InstanceUser"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public ChatSettingsRights? ChatSettingsRights { get; set; }
|
||||
public ChatBotRights? ChatBotRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.ConfigurationRights"/> of the <see cref="InstanceUser"/>
|
||||
|
||||
+6
-6
@@ -6,8 +6,8 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// <summary>
|
||||
/// Manage the server chat bots
|
||||
/// </summary>
|
||||
[Model(RightsType.ChatSettings, RequiresInstance = true, CanList = true, CanCrud = true, ReadRight = ChatSettingsRights.Read)]
|
||||
public class ChatSettings
|
||||
[Model(RightsType.ChatBots, RequiresInstance = true, CanList = true, CanCrud = true, ReadRight = ChatBotRights.Read)]
|
||||
public class ChatBot
|
||||
{
|
||||
/// <summary>
|
||||
/// The settings id
|
||||
@@ -18,26 +18,26 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// <summary>
|
||||
/// The name of the connection
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = ChatSettingsRights.WriteName)]
|
||||
[Permissions(WriteRight = ChatBotRights.WriteName)]
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the connection is enabled
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = ChatSettingsRights.WriteEnabled)]
|
||||
[Permissions(WriteRight = ChatBotRights.WriteEnabled)]
|
||||
public bool? Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ChatProvider"/> used for the connection
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = ChatSettingsRights.WriteProvider)]
|
||||
[Permissions(WriteRight = ChatBotRights.WriteProvider)]
|
||||
public ChatProvider? Provider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The information used to connect to the <see cref="Provider"/>
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = ChatSettingsRights.ReadConnectionString, WriteRight = ChatSettingsRights.ReadConnectionString)]
|
||||
[Permissions(ReadRight = ChatBotRights.ReadConnectionString, WriteRight = ChatBotRights.ReadConnectionString)]
|
||||
[Required]
|
||||
public string ConnectionString { get; set; }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
@@ -13,5 +14,12 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = DreamMakerRights.SetDme)]
|
||||
public string ProjectName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The port used during compilation to validate the TGS API
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = DreamMakerRights.SetApiValidationPort)]
|
||||
[Required]
|
||||
public ushort? ApiValidationPort { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,6 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// The <see cref="Rights"/> required to cancel the <see cref="Job"/>
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public int? CancelRight { get; set; }
|
||||
public ulong? CancelRight { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Tgstation.Server.Api.Models
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a JWT returned by the API
|
||||
@@ -9,5 +11,10 @@
|
||||
/// The value of the JWT
|
||||
/// </summary>
|
||||
public string Bearer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the <see cref="Token"/> expires
|
||||
/// </summary>
|
||||
public DateTimeOffset? ExpiresAt { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// Rights for <see cref="Models.Administration"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum AdministrationRights
|
||||
public enum AdministrationRights : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// Rights for <see cref="Models.Byond"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ByondRights
|
||||
public enum ByondRights : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights
|
||||
|
||||
+11
-11
@@ -3,49 +3,49 @@
|
||||
namespace Tgstation.Server.Api.Rights
|
||||
{
|
||||
/// <summary>
|
||||
/// Rights for <see cref="Models.ChatSettings"/>
|
||||
/// Rights for <see cref="Models.ChatBot"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ChatSettingsRights
|
||||
public enum ChatBotRights : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Internal.ChatSettings.Enabled"/>
|
||||
/// User can change <see cref="Models.Internal.ChatBot.Enabled"/>
|
||||
/// </summary>
|
||||
WriteEnabled = 1,
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Internal.ChatSettings.Provider"/>
|
||||
/// User can change <see cref="Models.Internal.ChatBot.Provider"/>
|
||||
/// </summary>
|
||||
WriteProvider = 2,
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.ChatSettings.Channels"/>
|
||||
/// User can change <see cref="Models.ChatBot.Channels"/>
|
||||
/// </summary>
|
||||
WriteChannels = 4,
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Internal.ChatSettings.ConnectionString"/>
|
||||
/// User can change <see cref="Models.Internal.ChatBot.ConnectionString"/>
|
||||
/// </summary>
|
||||
WriteConnectionString = 8,
|
||||
/// <summary>
|
||||
/// User can read <see cref="Models.Internal.ChatSettings.ConnectionString"/> requires <see cref="Read"/>
|
||||
/// User can read <see cref="Models.Internal.ChatBot.ConnectionString"/> requires <see cref="Read"/>
|
||||
/// </summary>
|
||||
ReadConnectionString = 16,
|
||||
/// <summary>
|
||||
/// User can read all chat settings except <see cref="Models.Internal.ChatSettings.ConnectionString"/>
|
||||
/// User can read all chat settings except <see cref="Models.Internal.ChatBot.ConnectionString"/>
|
||||
/// </summary>
|
||||
Read = 32,
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Internal.ChatSettings.Name"/>
|
||||
/// User can change <see cref="Models.Internal.ChatBot.Name"/>
|
||||
/// </summary>
|
||||
WriteName = 32,
|
||||
/// <summary>
|
||||
/// User can create new <see cref="Models.ChatSettings"/>
|
||||
/// User can create new <see cref="Models.ChatBot"/>
|
||||
/// </summary>
|
||||
Create = 64,
|
||||
/// <summary>
|
||||
/// User can delete <see cref="Models.ChatSettings"/>
|
||||
/// User can delete <see cref="Models.ChatBot"/>
|
||||
/// </summary>
|
||||
Delete = 128
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// Rights for <see cref="Models.ConfigurationFile"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ConfigurationRights
|
||||
public enum ConfigurationRights : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// Rights for <see cref="Models.DreamDaemon"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum DreamDaemonRights
|
||||
public enum DreamDaemonRights : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// Rights for <see cref="Models.DreamMaker"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum DreamMakerRights
|
||||
public enum DreamMakerRights : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights
|
||||
@@ -27,6 +27,14 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// <summary>
|
||||
/// User may modify <see cref="Models.Internal.DreamMakerSettings.ProjectName"/>
|
||||
/// </summary>
|
||||
SetDme = 8
|
||||
SetDme = 8,
|
||||
/// <summary>
|
||||
/// User may modify <see cref="Models.Internal.DreamMakerSettings.ApiValidationPort"/>
|
||||
/// </summary>
|
||||
SetApiValidationPort = 16,
|
||||
/// <summary>
|
||||
/// User may list and read all <see cref="Models.CompileJob"/>s
|
||||
/// </summary>
|
||||
List = 32
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// Rights for managing <see cref="Models.Instance"/>s
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum InstanceManagerRights
|
||||
public enum InstanceManagerRights : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// Rights for an <see cref="Models.Instance"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum InstanceUserRights
|
||||
public enum InstanceUserRights : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// Rights for a <see cref="Models.Repository"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum RepositoryRights
|
||||
public enum RepositoryRights : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
{ RightsType.Byond, typeof(ByondRights) },
|
||||
{ RightsType.DreamMaker, typeof(DreamMakerRights) },
|
||||
{ RightsType.DreamDaemon, typeof(DreamDaemonRights) },
|
||||
{ RightsType.ChatSettings, typeof(ChatSettingsRights) },
|
||||
{ RightsType.ChatBots, typeof(ChatBotRights) },
|
||||
{ RightsType.Configuration, typeof(ConfigurationRights) },
|
||||
{ RightsType.InstanceUser, typeof(InstanceUserRights) }
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// <summary>
|
||||
/// The type of rights a model uses
|
||||
/// </summary>
|
||||
public enum RightsType
|
||||
public enum RightsType : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="AdministrationRights"/>
|
||||
@@ -30,9 +30,9 @@
|
||||
/// </summary>
|
||||
DreamDaemon,
|
||||
/// <summary>
|
||||
/// <see cref="ChatSettingsRights"/>
|
||||
/// <see cref="ChatBotRights"/>
|
||||
/// </summary>
|
||||
ChatSettings,
|
||||
ChatBots,
|
||||
/// <summary>
|
||||
/// <see cref="ConfigurationRights"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Tgstation.Server.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a route to a server action
|
||||
/// </summary>
|
||||
public sealed class Route
|
||||
{
|
||||
/// <summary>
|
||||
/// The path to the action
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The method of the action
|
||||
/// </summary>
|
||||
public HttpMethod Method { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <paramref name="host"/> portion to <see cref="Path"/>
|
||||
/// </summary>
|
||||
/// <param name="host">The host address</param>
|
||||
/// <returns>A combined <paramref name="host"/> and <see cref="Path"/> <see cref="Uri"/></returns>
|
||||
public Uri Flatten(Uri host) => new Uri(String.Concat(host.ToString().TrimEnd('/'), Path));
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets routes for a given model
|
||||
/// </summary>
|
||||
public static class RouteHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Read a <typeparamref name="TModel"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The model type to read</typeparam>
|
||||
/// <param name="objectId">The optional ID to pass in</param>
|
||||
/// <returns>A route to the read action</returns>
|
||||
static Route Read<TModel>(long? objectId) where TModel : class
|
||||
{
|
||||
var result = new Route { Path = String.Concat('/', typeof(TModel).Name), Method = HttpMethod.Get };
|
||||
if (objectId.HasValue)
|
||||
result.Path = String.Concat(result.Path, '/', objectId);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Route"/> to the read action for a given <typeparamref name="TModel"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The model to read</typeparam>
|
||||
/// <param name="instance"><see cref="Instance"/> to read from if required</param>
|
||||
/// <returns>A <see cref="Route"/> to the read action</returns>
|
||||
public static Route Read<TModel>(Instance instance) where TModel : class
|
||||
{
|
||||
var model = (ModelAttribute)typeof(TModel).GetCustomAttributes(typeof(ModelAttribute), false).FirstOrDefault();
|
||||
if (model == default(ModelAttribute))
|
||||
throw new InvalidOperationException("TModel must have the ModelAttribute");
|
||||
if (model.RequiresInstance ^ instance != null)
|
||||
throw (model.RequiresInstance ? new ArgumentNullException(nameof(instance)) : new ArgumentException("Instance is not used for this route!", nameof(instance)));
|
||||
return Read<TModel>(instance?.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Route"/> to the update action for a given <typeparamref name="TModel"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The model to update</typeparam>
|
||||
/// <returns>A <see cref="Route"/> to the update action</returns>
|
||||
public static Route Update<TModel>(Instance instance) where TModel : class
|
||||
{
|
||||
var result = Read<TModel>(instance);
|
||||
result.Method = HttpMethod.Post;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Route"/> to the list action for a given <typeparamref name="TModel"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The model to list</typeparam>
|
||||
/// <returns>A <see cref="Route"/> to the list action</returns>
|
||||
public static Route List<TModel>(Instance instance) where TModel : class
|
||||
{
|
||||
var result = Read<TModel>(instance);
|
||||
result.Path = String.Concat(result.Path, '/', nameof(List));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Route"/> to the create action for a given <typeparamref name="TModel"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The model to create</typeparam>
|
||||
/// <returns>A <see cref="Route"/> to the create action</returns>
|
||||
public static Route Create<TModel>(Instance instance) where TModel : class
|
||||
{
|
||||
var result = Read<TModel>(instance);
|
||||
result.Method = HttpMethod.Put;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Route"/> to the delete action for a given <typeparamref name="TModel"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The model to delete</typeparam>
|
||||
/// <returns>A <see cref="Route"/> to the delete action</returns>
|
||||
public static Route Delete<TModel>(Instance instance) where TModel : class
|
||||
{
|
||||
var result = Read<TModel>(instance);
|
||||
result.Method = HttpMethod.Delete;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Route"/> to a given <paramref name="job"/>
|
||||
/// </summary>
|
||||
/// <param name="job">The <see cref="Job"/> to get</param>
|
||||
/// <returns>A <see cref="Route"/> to the get action</returns>
|
||||
public static Route GetJob(Job job) => Read<Job>(job.Id);
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Route"/> to a given <paramref name="user"/>'s token list
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="User"/> to list tokens for</param>
|
||||
/// <returns>A <see cref="Route"/> to the list action</returns>
|
||||
public static Route ListUserTokens(User user)
|
||||
{
|
||||
var result = List<Token>(null);
|
||||
result.Path = String.Concat(result.Path, '/', user.Id);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Route"/> to a server's version
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Route ServerVersion() => new Route { Path = "/", Method = HttpMethod.Get };
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Route"/> to read a <see cref="ConfigurationFile"/> file
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="Instance"/> the <see cref="ConfigurationFile"/> file resides in</param>
|
||||
/// <param name="path">The path to the file in the <see cref="ConfigurationFile"/> directory</param>
|
||||
/// <returns>A <see cref="Route"/> to the read action</returns>
|
||||
public static Route ReadFile(Instance instance, string path) => new Route { Path = String.Concat("/Configuration/", instance?.Id ?? throw new ArgumentNullException(nameof(instance)), '/', path?.TrimStart('/') ?? throw new ArgumentNullException(nameof(path))), Method = HttpMethod.Get };
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Route"/> to list <see cref="ConfigurationFile"/> files for a <paramref name="directory"/>
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="Instance"/> the <see cref="ConfigurationFile"/> file resides in</param>
|
||||
/// <param name="directory">The <see cref="ConfigurationFile"/> directory to list</param>
|
||||
/// <returns>A <see cref="Route"/> to the read action</returns>
|
||||
public static Route ListFiles(Instance instance, string directory)
|
||||
{
|
||||
var result = ReadFile(instance, directory);
|
||||
result.Path = String.Concat("/ConfigurationList", result.Path.Substring(result.Path.IndexOf('/', 1)));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Route"/> to create a <see cref="ConfigurationFile"/> file
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="Instance"/> the <see cref="ConfigurationFile"/> file resides in</param>
|
||||
/// <param name="path">The path to the file in the <see cref="ConfigurationFile"/> directory</param>
|
||||
/// <returns>A <see cref="Route"/> to the create action</returns>
|
||||
public static Route CreateFile(Instance instance, string path)
|
||||
{
|
||||
var result = ReadFile(instance, path);
|
||||
result.Method = HttpMethod.Put;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Route"/> to delete a <see cref="ConfigurationFile"/> file
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="Instance"/> the <see cref="ConfigurationFile"/> file resides in</param>
|
||||
/// <param name="path">The path to the file in the <see cref="ConfigurationFile"/> directory</param>
|
||||
/// <returns>A <see cref="Route"/> to the delete action</returns>
|
||||
public static Route DeleteFile(Instance instance, string path)
|
||||
{
|
||||
var result = ReadFile(instance, path);
|
||||
result.Method = HttpMethod.Delete;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Tgstation.Server.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Routes to a server actions
|
||||
/// </summary>
|
||||
public static class Routes
|
||||
{
|
||||
/// <summary>
|
||||
/// The root controller
|
||||
/// </summary>
|
||||
public const string Root = "/";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.Administration"/> controller
|
||||
/// </summary>
|
||||
public const string Administration = Root + nameof(Models.Administration);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.User"/> controller
|
||||
/// </summary>
|
||||
public const string User = Root + nameof(Models.User);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.Instance"/> controller
|
||||
/// </summary>
|
||||
public const string InstanceManager = Root + nameof(Models.Instance);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.Byond"/> controller
|
||||
/// </summary>
|
||||
public const string Byond = Root + nameof(Models.Byond);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.Repository"/> controller
|
||||
/// </summary>
|
||||
public const string Repository = Root + nameof(Models.Repository);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.DreamDaemon"/> controller
|
||||
/// </summary>
|
||||
public const string DreamDaemon = Root + nameof(Models.DreamDaemon);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.ConfigurationFile"/> controller
|
||||
/// </summary>
|
||||
public const string Configuration = Root + "Config";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.InstanceUser"/> controller
|
||||
/// </summary>
|
||||
public const string InstanceUser = Root + nameof(Models.InstanceUser);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.ChatBot"/> controller
|
||||
/// </summary>
|
||||
public const string Chat = Root + "Chat";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.DreamMaker"/> controller
|
||||
/// </summary>
|
||||
public const string DreamMaker = Root + nameof(Models.DreamMaker);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.Job"/> controller
|
||||
/// </summary>
|
||||
public const string Jobs = Root + nameof(Models.Job);
|
||||
|
||||
/// <summary>
|
||||
/// Apply an <paramref name="id"/> postfix to a <paramref name="route"/>
|
||||
/// </summary>
|
||||
/// <param name="route">The route</param>
|
||||
/// <param name="id">The ID</param>
|
||||
/// <returns>The <paramref name="route"/> with <paramref name="id"/> appended</returns>
|
||||
public static string SetID(string route, long id) => String.Format(CultureInfo.InvariantCulture, "{0}/{1}", route, id);
|
||||
|
||||
/// <summary>
|
||||
/// Get the /List postfix for a <paramref name="route"/>
|
||||
/// </summary>
|
||||
/// <param name="route">The route</param>
|
||||
/// <returns>The <paramref name="route"/> with /List appended</returns>
|
||||
public static string List(string route) => String.Format(CultureInfo.InvariantCulture, "{0}/List", route);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace Tgstation.Server.Api.Routes
|
||||
{
|
||||
/// <summary>
|
||||
/// Routes for <see cref="Models.Administration"/>
|
||||
/// </summary>
|
||||
public static class Administration
|
||||
{
|
||||
/// <summary>
|
||||
/// The base route
|
||||
/// </summary>
|
||||
public const string Base = "/" + nameof(Administration);
|
||||
}
|
||||
}
|
||||
@@ -25,12 +25,12 @@
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<DocumentationFile>bin\Release\netstandard2.0\Tgstation.Server.Api.xml</DocumentationFile>
|
||||
<NoWarn>1701;1702;1705;CA2227;CA1819</NoWarn>
|
||||
<NoWarn>1701;1702;1705;CA2227;CA1819;CA1028</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<LangVersion>latest</LangVersion>
|
||||
<NoWarn>1701;1702;1705;CA2227;CA1819</NoWarn>
|
||||
<NoWarn>1701;1702;1705;CA2227;CA1819;CA1028</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class AdministrationClient : IAdministrationClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="apiClient"/> for the <see cref="AdministrationClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="AdministrationClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
public AdministrationClient(IApiClient apiClient)
|
||||
{
|
||||
this.apiClient = apiClient;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Administration> Read(CancellationToken cancellationToken) => apiClient.Read<Administration>(Routes.Administration, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Update(Administration administration, CancellationToken cancellationToken) => apiClient.Update(Routes.Administration, administration, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class ApiClient : IApiClient
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Uri Url { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ApiHeaders Headers { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public TimeSpan Timeout
|
||||
{
|
||||
get => httpClient.Timeout;
|
||||
set => httpClient.Timeout = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="HttpClient"/> for the <see cref="IApiClient"/>
|
||||
/// </summary>
|
||||
readonly HttpClient httpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ApiClient"/>
|
||||
/// </summary>
|
||||
/// <param name="url">The value of <see cref="Url"/></param>
|
||||
/// <param name="apiHeaders">The value of <see cref="ApiHeaders"/></param>
|
||||
public ApiClient(Uri url, ApiHeaders apiHeaders)
|
||||
{
|
||||
Url = url ?? throw new ArgumentNullException(nameof(url));
|
||||
Headers = apiHeaders ?? throw new ArgumentNullException(nameof(apiHeaders));
|
||||
|
||||
httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => httpClient.Dispose();
|
||||
|
||||
/// <summary>
|
||||
/// Main request method
|
||||
/// </summary>
|
||||
/// <param name="route">The route to run</param>
|
||||
/// <param name="body">The body of the request</param>
|
||||
/// <param name="method">The method of the request</param>
|
||||
/// <param name="instanceId">The optional <see cref="Api.Models.Instance.Id"/> for the request</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response on success</returns>
|
||||
async Task<TResult> RunRequest<TResult>(string route, object body, HttpMethod method, long? instanceId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (route == null)
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
if (method == null)
|
||||
throw new ArgumentNullException(nameof(method));
|
||||
if (body == null && (method == HttpMethod.Post || method == HttpMethod.Put))
|
||||
throw new InvalidOperationException("Body cannot be null for POST or PUT!");
|
||||
|
||||
var fullUri = new Uri(Url, route);
|
||||
|
||||
HttpContent content = null;
|
||||
if (body != null)
|
||||
content = new StringContent(JsonConvert.SerializeObject(body));
|
||||
|
||||
Task<HttpResponseMessage> task;
|
||||
lock (this)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Clear();
|
||||
Headers.SetRequestHeaders(httpClient.DefaultRequestHeaders, instanceId);
|
||||
|
||||
if (method == HttpMethod.Get)
|
||||
task = httpClient.GetAsync(route);
|
||||
else if (method == HttpMethod.Put)
|
||||
task = httpClient.PutAsync(fullUri, content, cancellationToken);
|
||||
else if (method == HttpMethod.Post)
|
||||
task = httpClient.PostAsync(fullUri, content, cancellationToken);
|
||||
else if (method == HttpMethod.Delete)
|
||||
task = httpClient.DeleteAsync(fullUri, cancellationToken);
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
var response = await task.ConfigureAwait(false);
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
if (!response.IsSuccessStatusCode) {
|
||||
ErrorMessage errorMessage = null;
|
||||
try
|
||||
{
|
||||
//check if json serializes to an error message
|
||||
errorMessage = JsonConvert.DeserializeObject<ErrorMessage>(json);
|
||||
}
|
||||
catch (JsonSerializationException) { }
|
||||
|
||||
switch (response.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.BadRequest:
|
||||
//validate our api version is compatible
|
||||
if(errorMessage != null && ApiHeaders.CheckCompatibility(errorMessage.SeverApiVersion))
|
||||
throw new ApiMismatchException(errorMessage);
|
||||
goto default;
|
||||
case HttpStatusCode.Unauthorized:
|
||||
throw new UnauthorizedException();
|
||||
case HttpStatusCode.RequestTimeout:
|
||||
throw new RequestTimeoutException();
|
||||
case HttpStatusCode.Forbidden:
|
||||
throw new InsufficientPermissionsException();
|
||||
case HttpStatusCode.Gone:
|
||||
case HttpStatusCode.NotFound:
|
||||
case HttpStatusCode.Conflict:
|
||||
throw new ConflictException(errorMessage, response.StatusCode);
|
||||
case HttpStatusCode.NotImplemented:
|
||||
throw new MethodNotSupportedException();
|
||||
case HttpStatusCode.InternalServerError:
|
||||
//response
|
||||
throw new ServerErrorException(json); //json is html
|
||||
case (HttpStatusCode)429: //rate limited
|
||||
response.Headers.TryGetValues("Retry-After", out var values);
|
||||
throw new RateLimitException(values?.FirstOrDefault());
|
||||
default:
|
||||
throw new ApiConflictException(errorMessage, response.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(json))
|
||||
json = JsonConvert.SerializeObject(new object());
|
||||
|
||||
return JsonConvert.DeserializeObject<TResult>(json);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Create<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Put, null, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Read<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Get, null, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Update<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Post, null, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Update<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Post, null, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Update<TBody>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<object>(route, body, HttpMethod.Post, null, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Create<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Put, null, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(string route, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Delete, null, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Create<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Put, instanceId, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Read<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Get, instanceId, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Update<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Post, instanceId, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Delete, instanceId, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Create<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Put, instanceId, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using Tgstation.Server.Api;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class ApiClientFactory : IApiClientFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders) => new ApiClient(url, apiHeaders);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the server returns an unknown response
|
||||
/// </summary>
|
||||
public sealed class ApiConflictException : ClientException
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ApiConflictException"/> using an <paramref name="errorMessage"/>
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The <see cref="ErrorMessage"/> for the <see cref="ClientException"/></param>
|
||||
/// <param name="statusCode">The <see cref="HttpStatusCode"/> for the <see cref="ClientException"/></param>
|
||||
public ApiConflictException(ErrorMessage errorMessage, HttpStatusCode statusCode) : base(errorMessage, statusCode) { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ApiConflictException"/>
|
||||
/// </summary>
|
||||
public ApiConflictException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ApiConflictException"/> with a <paramref name="message"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
public ApiConflictException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ApiConflictException"/> with a <paramref name="message"/> and <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public ApiConflictException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the API version of the client is not compatible with the server's
|
||||
/// </summary>
|
||||
public sealed class ApiMismatchException : ClientException
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ApiMismatchException"/> using an <paramref name="errorMessage"/>
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The <see cref="ErrorMessage"/> for the <see cref="ClientException"/></param>
|
||||
public ApiMismatchException(ErrorMessage errorMessage) : base(errorMessage, HttpStatusCode.BadRequest)
|
||||
{
|
||||
if (errorMessage == null)
|
||||
throw new ArgumentNullException(nameof(errorMessage));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ApiMismatchException"/>
|
||||
/// </summary>
|
||||
public ApiMismatchException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ApiMismatchException"/> with a <paramref name="message"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
public ApiMismatchException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ApiMismatchException"/> with a <paramref name="message"/> and <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public ApiMismatchException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Exceptions thrown by <see cref="IServerClient"/>s
|
||||
/// </summary>
|
||||
public abstract class ClientException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="HttpStatusCode"/> of the <see cref="ClientException"/>
|
||||
/// </summary>
|
||||
HttpStatusCode StatusCode { get; }
|
||||
|
||||
Version ServerApiVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ClientException"/> using an <paramref name="errorMessage"/> and <paramref name="statusCode"/>
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The <see cref="ErrorMessage"/> associated with the <see cref="ClientException"/></param>
|
||||
/// <param name="statusCode">The <see cref="HttpStatusCode"/> of the <see cref="ClientException"/></param>
|
||||
protected ClientException(ErrorMessage errorMessage, HttpStatusCode statusCode) : base(errorMessage == null ? throw new ArgumentNullException(nameof(errorMessage)) : errorMessage.Message ?? "Unknown Error")
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
ServerApiVersion = errorMessage?.SeverApiVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ClientException"/>
|
||||
/// </summary>
|
||||
protected ClientException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ClientException"/> with a <paramref name="message"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
protected ClientException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ClientException"/> with a <paramref name="message"/> and <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
protected ClientException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class ByondClient : IByondClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="ByondClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="ByondClient"/>
|
||||
/// </summary>
|
||||
readonly Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ByondClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="instance">The value of <see cref="Instance"/></param>
|
||||
public ByondClient(IApiClient apiClient, Instance instance)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Byond> Read(CancellationToken cancellationToken) => apiClient.Read<Byond>(Routes.Byond, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Byond> Update(Byond byond, CancellationToken cancellationToken) => apiClient.Update<Byond, Byond>(Routes.Byond, byond, instance.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class ChatBotsClient : IChatBotsClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="ChatBotsClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="ChatBotsClient"/>
|
||||
/// </summary>
|
||||
readonly Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ChatBotsClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
public ChatBotsClient(IApiClient apiClient, Instance instance)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ChatBot> Create(ChatBot settings, CancellationToken cancellationToken) => apiClient.Create<ChatBot, ChatBot>(Routes.Chat, settings, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(ChatBot settings, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.Chat, settings.Id), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<ChatBot>> List(CancellationToken cancellationToken) => apiClient.Create<IReadOnlyList<ChatBot>>(Routes.List(Routes.Chat), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ChatBot> Update(ChatBot settings, CancellationToken cancellationToken) => apiClient.Update<ChatBot, ChatBot>(Routes.Chat, settings, instance.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class ConfigurationClient : IConfigurationClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="ConfigurationClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="ConfigurationClient"/>
|
||||
/// </summary>
|
||||
readonly Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ConfigurationClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
public ConfigurationClient(IApiClient apiClient, Instance instance)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<ConfigurationFile>> List(string directory, CancellationToken cancellationToken)
|
||||
{
|
||||
if (directory == null)
|
||||
directory = String.Empty;
|
||||
return apiClient.Read<IReadOnlyList<ConfigurationFile>>(Routes.List(Routes.Configuration) + directory, instance.Id, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ConfigurationFile> Read(ConfigurationFile file, CancellationToken cancellationToken)
|
||||
{
|
||||
if (file == null)
|
||||
throw new ArgumentNullException(nameof(file));
|
||||
return apiClient.Read<ConfigurationFile>(Routes.Configuration + file.Path, instance.Id, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ConfigurationFile> Write(ConfigurationFile file, CancellationToken cancellationToken) => apiClient.Update<ConfigurationFile, ConfigurationFile>(Routes.Configuration, file, instance.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DreamDaemonClient : IDreamDaemonClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="DreamDaemonClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="DreamDaemonClient"/>
|
||||
/// </summary>
|
||||
readonly Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DreamDaemonClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
public DreamDaemonClient(IApiClient apiClient, Instance instance)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Shutdown(CancellationToken cancellationToken) => apiClient.Delete(Routes.DreamDaemon, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<DreamDaemon> Start(CancellationToken cancellationToken) => apiClient.Create<DreamDaemon>(Routes.DreamDaemon, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<DreamDaemon> Read(CancellationToken cancellationToken) => apiClient.Read<DreamDaemon>(Routes.DreamDaemon, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<DreamDaemon> Update(DreamDaemon dreamDaemon, CancellationToken cancellationToken) => apiClient.Update<DreamDaemon, DreamDaemon>(Routes.DreamDaemon, dreamDaemon, instance.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DreamMakerClient : IDreamMakerClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="DreamMakerClient"/>
|
||||
/// </summary>
|
||||
private IApiClient apiClient;
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="DreamMakerClient"/>
|
||||
/// </summary>
|
||||
private Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DreamMakerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="instance">The value of <see cref="Instance"/></param>
|
||||
public DreamMakerClient(IApiClient apiClient, Instance instance)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Job> Compile(CancellationToken cancellationToken) => apiClient.Create<Job>(Routes.DreamMaker, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<DreamMaker> Read(CancellationToken cancellationToken) => apiClient.Read<DreamMaker>(Routes.DreamMaker, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<DreamMaker> Update(DreamMaker dreamMaker, CancellationToken cancellationToken) => apiClient.Update<DreamMaker, DreamMaker>(Routes.DreamMaker, dreamMaker, instance.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,27 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing the <see cref="Byond"/> installation
|
||||
/// </summary>
|
||||
public interface IByondClient : IRightsClient<ByondRights>
|
||||
public interface IByondClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="Byond"/> represented by the <see cref="IByondClient"/>
|
||||
/// Get the <see cref="Byond"/> information
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Byond"/> represented by the <see cref="IByondClient"/></returns>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Byond"/> information</returns>
|
||||
Task<Byond> Read(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the installed BYOND <see cref="Version"/>
|
||||
/// Updates the <see cref="Byond"/> information
|
||||
/// </summary>
|
||||
/// <param name="version">The <see cref="Version"/> to set to active</param>
|
||||
/// <param name="byond">The <see cref="Byond"/> information to update</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task SetActiveVersion(Version version, CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the updated <see cref="Byond"/> information</returns>
|
||||
Task<Byond> Update(Byond byond, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing the chat bots
|
||||
/// </summary>
|
||||
public interface IChatBotsClient
|
||||
{
|
||||
/// <summary>
|
||||
/// List the <see cref="ChatBot"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of the <see cref="ChatBot"/> of the server</returns>
|
||||
Task<IReadOnlyList<ChatBot>> List(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ChatBot"/>
|
||||
/// </summary>
|
||||
/// <param name="settings">The <see cref="ChatBot"/> to create</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the new <see cref="ChatBot"/></returns>
|
||||
Task<ChatBot> Create(ChatBot settings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Updates a <see cref="ChatBot"/> setttings
|
||||
/// </summary>
|
||||
/// <param name="settings">The <see cref="ChatBot"/> to update</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the updated <see cref="ChatBot"/></returns>
|
||||
Task<ChatBot> Update(ChatBot settings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a <see cref="ChatBot"/>
|
||||
/// </summary>
|
||||
/// <param name="settings">The <see cref="ChatBot"/> to delete</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Delete(ChatBot settings, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing the chat bots
|
||||
/// </summary>
|
||||
public interface IChatSettingsClient : IRightsClient<ChatSettingsRights>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="ChatSettings"/> represented by the <see cref="IChatSettingsClient"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="ChatSettings"/> represented by the <see cref="IChatSettingsClient"/></returns>
|
||||
Task<ChatSettings> Read(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="ChatSettings"/> setttings
|
||||
/// </summary>
|
||||
/// <param name="chat">The <see cref="ChatSettings"/> to update</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Update(ChatSettings chat, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing <see cref="ConfigurationFile"/> files
|
||||
/// </summary>
|
||||
public interface IConfigurationClient : IRightsClient<ConfigurationRights>
|
||||
public interface IConfigurationClient
|
||||
{
|
||||
/// <summary>
|
||||
/// List configuration files
|
||||
@@ -26,30 +24,14 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <param name="file">The <see cref="ConfigurationFile"/> file to read</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Read(ConfigurationFile file, CancellationToken cancellationToken);
|
||||
Task<ConfigurationFile> Read(ConfigurationFile file, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Overwrite a <see cref="ConfigurationFile"/> file with integrity checks
|
||||
/// Overwrite a <see cref="ConfigurationFile"/> file
|
||||
/// </summary>
|
||||
/// <param name="file">The <see cref="ConfigurationFile"/> file to write</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Write(ConfigurationFile file, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create/overwrite a <see cref="ConfigurationFile"/> file
|
||||
/// </summary>
|
||||
/// <param name="file">The <see cref="ConfigurationFile"/> file to write</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Create(ConfigurationFile file, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a <see cref="ConfigurationFile"/> file
|
||||
/// </summary>
|
||||
/// <param name="file">The <see cref="ConfigurationFile"/> file to delete</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Delete(ConfigurationFile file, CancellationToken cancellationToken);
|
||||
Task<ConfigurationFile> Write(ConfigurationFile file, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
public interface IDreamDaemonClient: IRightsClient<DreamDaemonRights>
|
||||
public interface IDreamDaemonClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="DreamDaemon"/> represented by the <see cref="IDreamDaemonClient"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="DreamDaemon"/> represented by the <see cref="IDreamDaemonClient"/></returns>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="DreamDaemon"/> information</returns>
|
||||
Task<DreamDaemon> Read(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Start <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Start(CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="DreamDaemon"/> information</returns>
|
||||
Task<DreamDaemon> Start(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Shutdown <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="DreamDaemon"/> information</returns>
|
||||
Task Shutdown(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -36,7 +35,7 @@ namespace Tgstation.Server.Client.Components
|
||||
/// </summary>
|
||||
/// <param name="dreamDaemon">The <see cref="DreamDaemon"/> to update</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Update(DreamDaemon dreamDaemon, CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="DreamDaemon"/> information</returns>
|
||||
Task<DreamDaemon> Update(DreamDaemon dreamDaemon, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing the compiler
|
||||
/// </summary>
|
||||
public interface IDreamMakerClient : IRightsClient<DreamMakerRights>
|
||||
public interface IDreamMakerClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="DreamMaker"/> represented by the <see cref="IDreamMakerClient"/>
|
||||
/// Get the <see cref="DreamMaker"/> information
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="DreamMaker"/> represented by the <see cref="IDreamMakerClient"/></returns>
|
||||
Task<ChatSettings> Read(CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="DreamMaker"/> information</returns>
|
||||
Task<DreamMaker> Read(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="DreamMaker"/> setttings
|
||||
@@ -23,13 +22,13 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <param name="dreamMaker">The <see cref="DreamMaker"/> to update</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Update(DreamMaker dreamMaker, CancellationToken cancellationToken);
|
||||
Task<DreamMaker> Update(DreamMaker dreamMaker, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Compile the current repository revision
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Compile(CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Job"/> for the compile</returns>
|
||||
Task<Job> Compile(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
@@ -9,6 +8,11 @@ namespace Tgstation.Server.Client.Components
|
||||
/// </summary>
|
||||
public interface IInstanceClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> used to create the <see cref="IInstanceClient"/>
|
||||
/// </summary>
|
||||
Instance Metadata { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="IByondClient"/>
|
||||
/// </summary>
|
||||
@@ -19,10 +23,10 @@ namespace Tgstation.Server.Client.Components
|
||||
/// </summary>
|
||||
IRepositoryClient Repository { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="IDreamDaemonClient"/>
|
||||
/// </summary>
|
||||
IDreamDaemonClient DreamDaemon { get; }
|
||||
/// <summary>
|
||||
/// Access the <see cref="IDreamDaemonClient"/>
|
||||
/// </summary>
|
||||
IDreamDaemonClient DreamDaemon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="IConfigurationClient"/>
|
||||
@@ -35,20 +39,18 @@ namespace Tgstation.Server.Client.Components
|
||||
IInstanceUserClient Users { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="IChatSettingsClient"/>
|
||||
/// Access the <see cref="IChatBotsClient"/>
|
||||
/// </summary>
|
||||
IChatSettingsClient Chat { get; }
|
||||
|
||||
IChatBotsClient ChatBots { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="IDreamMakerClient"/>
|
||||
/// </summary>
|
||||
IDreamMakerClient DreamMaker { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Instance"/> represented by the <see cref="IInstanceClient"/>
|
||||
/// Access the <see cref="IJobsClient"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Instance"/> represented by the <see cref="IInstanceClient"/></returns>
|
||||
Task<Instance> Read(CancellationToken cancellationToken);
|
||||
IJobsClient Jobs { get; }
|
||||
}
|
||||
}
|
||||
@@ -2,28 +2,50 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing <see cref="InstanceUser"/>s
|
||||
/// </summary>
|
||||
public interface IInstanceUserClient : IRightsClient<InstanceUserRights>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="InstanceUser"/>s in the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of <see cref="InstanceUser"/>s in the instance</returns>
|
||||
Task<IReadOnlyList<InstanceUser>> Read(CancellationToken cancellationToken);
|
||||
public interface IInstanceUserClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="InstanceUser"/> associated with the logged on user
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="InstanceUser"/> associated with the logged on user</returns>
|
||||
Task<InstanceUser> Read(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Update a <paramref name="instanceUser"/>
|
||||
/// </summary>
|
||||
/// <param name="instanceUser">The <see cref="InstanceUser"/> to update</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Update(InstanceUser instanceUser, CancellationToken cancellationToken);
|
||||
}
|
||||
/// <summary>
|
||||
/// Get the <see cref="InstanceUser"/>s in the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of <see cref="InstanceUser"/>s in the instance</returns>
|
||||
Task<IReadOnlyList<InstanceUser>> List(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Update a <paramref name="instanceUser"/>
|
||||
/// </summary>
|
||||
/// <param name="instanceUser">The <see cref="InstanceUser"/> to update</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task<InstanceUser> Update(InstanceUser instanceUser, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <paramref name="instanceUser"/>
|
||||
/// </summary>
|
||||
/// <param name="instanceUser">The <see cref="InstanceUser"/> to create</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> reulting in the new <see cref="InstanceUser"/></returns>
|
||||
Task<InstanceUser> Create(InstanceUser instanceUser, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a <paramref name="instanceUser"/>
|
||||
/// </summary>
|
||||
/// <param name="instanceUser">The <see cref="InstanceUser"/> to delete</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Delete(InstanceUser instanceUser, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
@@ -11,12 +12,20 @@ namespace Tgstation.Server.Client.Components
|
||||
public interface IJobsClient
|
||||
{
|
||||
/// <summary>
|
||||
/// List the active jobs the user can view
|
||||
/// List the <see cref="Api.Models.Internal.Job.Id"/>s in the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of active <see cref="Job"/>s the user can view</returns>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of the <see cref="Api.Models.Internal.Job.Id"/>s in the <see cref="Instance"/></returns>
|
||||
Task<IReadOnlyList<Job>> List(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <paramref name="job"/>
|
||||
/// </summary>
|
||||
/// <param name="job">The <see cref="Job"/> to get</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Job"/></returns>
|
||||
Task<Job> Read(Job job, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Cancels a <paramref name="job"/>
|
||||
/// </summary>
|
||||
@@ -24,5 +33,15 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Cancel(Job job, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Task{TResult}"/> that completes when a given <paramref name="job"/> is completed
|
||||
/// </summary>
|
||||
/// <param name="job">The <see cref="Job"/> to create a <see cref="Task"/> for</param>
|
||||
/// <param name="requeryRate">The rate in to poll the server for results</param>
|
||||
/// <param name="progressCallback">A <see cref="Action{T}"/> to run with 0-100 progress</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> which will trigger the cancellation of the <paramref name="job"/></param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a complete <see cref="Job"/></returns>
|
||||
Task<Job> CreateTaskFromJob(Job job, TimeSpan requeryRate, Action<int> progressCallback, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,29 +2,34 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing the <see cref="Repository"/>
|
||||
/// </summary>
|
||||
public interface IRepositoryClient : IRightsClient<RepositoryRights>
|
||||
public interface IRepositoryClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="Repository"/> represented by the <see cref="IRepositoryClient"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Repository"/> represented by the <see cref="IRepositoryClient"/></returns>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Repository"/></returns>
|
||||
Task<Repository> Read(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Update the <see cref="Repository"/>
|
||||
/// </summary>
|
||||
/// <param name="repository">The <see cref="Repository"/> to update</param>
|
||||
/// <param name="progressCallback">Optional action to take when progress is reported. Will either not be called for some operations, or be called with the numbers 1-100</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the updated <see cref="Repository"/></returns>
|
||||
Task<Repository> Update(Repository repository, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the <see cref="Repository"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Update(Repository repository, Action<int> progressCallback, CancellationToken cancellationToken);
|
||||
Task Delete(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class InstanceClient : IInstanceClient
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Instance Metadata { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IByondClient Byond { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRepositoryClient Repository { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDreamDaemonClient DreamDaemon { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IConfigurationClient Configuration { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IInstanceUserClient Users { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IChatBotsClient ChatBots { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDreamMakerClient DreamMaker { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IJobsClient Jobs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="InstanceClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="InstanceClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="instance">The value of <see cref="Metadata"/></param>
|
||||
public InstanceClient(IApiClient apiClient, Instance instance)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
Metadata = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
|
||||
Byond = new ByondClient(apiClient, instance);
|
||||
Repository = new RepositoryClient(apiClient, instance);
|
||||
DreamDaemon = new DreamDaemonClient(apiClient, instance);
|
||||
Configuration = new ConfigurationClient(apiClient, instance);
|
||||
Users = new InstanceUserClient(apiClient, instance);
|
||||
ChatBots = new ChatBotsClient(apiClient, instance);
|
||||
DreamMaker = new DreamMakerClient(apiClient, instance);
|
||||
Jobs = new JobsClient(apiClient, instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class InstanceUserClient : IInstanceUserClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="InstanceUserClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="InstanceUserClient"/>
|
||||
/// </summary>
|
||||
readonly Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InstanceUserClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
public InstanceUserClient(IApiClient apiClient, Instance instance)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<InstanceUser> Create(InstanceUser user, CancellationToken cancellationToken) => apiClient.Create<InstanceUser, InstanceUser>(Routes.InstanceUser, user, instance.Id, cancellationToken);
|
||||
|
||||
public Task Delete(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.InstanceUser, instanceUser.UserId.Value), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<InstanceUser> Read(CancellationToken cancellationToken) => apiClient.Read<InstanceUser>(Routes.InstanceUser, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<InstanceUser> Update(InstanceUser user, CancellationToken cancellationToken) => apiClient.Update<InstanceUser, InstanceUser>(Routes.InstanceUser, user, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<InstanceUser>> List(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<InstanceUser>>(Routes.List(Routes.InstanceUser), instance.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class JobsClient : IJobsClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="JobsClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="JobsClient"/>
|
||||
/// </summary>
|
||||
readonly Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="JobsClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="instance">The value of <see cref="Instance"/></param>
|
||||
public JobsClient(IApiClient apiClient, Instance instance)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Cancel(Job job, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.Jobs, job.Id), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<Job>> List(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<Job>>(Routes.List(Routes.Jobs), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Job> Read(Job job, CancellationToken cancellationToken) => apiClient.Read<Job>(Routes.SetID(Routes.Jobs, job.Id), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Job> CreateTaskFromJob(Job job, TimeSpan requeryRate, Action<int> progressCallback, CancellationToken cancellationToken)
|
||||
{
|
||||
if (job == null)
|
||||
throw new ArgumentNullException(nameof(job));
|
||||
|
||||
int? lastProgress = null;
|
||||
while (!job.StoppedAt.HasValue)
|
||||
{
|
||||
await Task.Delay(requeryRate, cancellationToken).ConfigureAwait(false);
|
||||
job = await Read(job, cancellationToken).ConfigureAwait(false);
|
||||
if(job.Progress.HasValue && job.Progress != lastProgress)
|
||||
{
|
||||
progressCallback(job.Progress.Value);
|
||||
lastProgress = job.Progress;
|
||||
}
|
||||
}
|
||||
return job;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class RepositoryClient : IRepositoryClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="RepositoryClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="RepositoryClient"/>
|
||||
/// </summary>
|
||||
readonly Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="RepositoryClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="instance"></param>
|
||||
public RepositoryClient(IApiClient apiClient, Instance instance)
|
||||
{
|
||||
this.apiClient = apiClient;
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(CancellationToken cancellationToken) => apiClient.Delete(Routes.Repository, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Repository> Read(CancellationToken cancellationToken) => apiClient.Read<Repository>(Routes.Repository, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Repository> Update(Repository repository, CancellationToken cancellationToken) => apiClient.Update<Repository, Repository>(Routes.Repository, repository, instance.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the client performs an action that would result in data conflict
|
||||
/// </summary>
|
||||
public sealed class ConflictException : ClientException
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ConflictException"/> with a <paramref name="errorMessage"/> and <paramref name="statusCode"/>
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The <see cref="ErrorMessage"/> for the <see cref="ClientException"/></param>
|
||||
/// <param name="statusCode">The <see cref="HttpStatusCode"/> for the <see cref="ClientException"/></param>
|
||||
public ConflictException(ErrorMessage errorMessage, HttpStatusCode statusCode) : base(errorMessage, statusCode)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ConflictException"/>
|
||||
/// </summary>
|
||||
public ConflictException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ConflictException"/> with a <paramref name="message"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
public ConflictException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ConflictException"/> with a <paramref name="message"/> and <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public ConflictException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -1,14 +1,13 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing server administration
|
||||
/// </summary>
|
||||
public interface IAdministrationClient : IRightsClient<AdministrationRights>
|
||||
public interface IAdministrationClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="Administration"/> represented by the <see cref="IAdministrationClient"/>
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Web interface for the API
|
||||
/// </summary>
|
||||
interface IApiClient : IDisposable
|
||||
{
|
||||
ApiHeaders Headers { get; }
|
||||
|
||||
Uri Url { get; }
|
||||
|
||||
TimeSpan Timeout { get; set; }
|
||||
|
||||
Task<TResult> Create<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken);
|
||||
Task<TResult> Create<TResult>(string route, CancellationToken cancellationToken);
|
||||
Task<TResult> Read<TResult>(string route, CancellationToken cancellationToken);
|
||||
Task<TResult> Update<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken);
|
||||
Task<TResult> Update<TResult>(string route, CancellationToken cancellationToken);
|
||||
Task Update<TBody>(string route, TBody body, CancellationToken cancellationToken);
|
||||
Task Delete(string route, CancellationToken cancellationToken);
|
||||
|
||||
Task<TResult> Create<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken);
|
||||
Task<TResult> Create<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
Task<TResult> Read<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
Task<TResult> Update<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken);
|
||||
Task Delete(string route, long instanceId, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Tgstation.Server.Api;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// For creating <see cref="IApiClient"/>s
|
||||
/// </summary>
|
||||
interface IApiClientFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an <see cref="IApiClient"/>
|
||||
/// </summary>
|
||||
/// <param name="url">The base <see cref="Uri"/></param>
|
||||
/// <param name="apiHeaders">The <see cref="ApiHeaders"/> for the <see cref="IApiClient"/></param>
|
||||
/// <returns>A new <see cref="IApiClient"/></returns>
|
||||
IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders);
|
||||
}
|
||||
}
|
||||
+16
-9
@@ -2,37 +2,37 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Client.Components;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing <see cref="Instance"/>s
|
||||
/// </summary>
|
||||
public interface IInstanceManagerClient : IRightsClient<InstanceUserRights>
|
||||
public interface IInstanceManagerClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all <see cref="IInstanceClient"/>s for <see cref="Instance"/>s the user can view
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of all <see cref="IInstanceClient"/>s for <see cref="Instance"/>s the user can view</returns>
|
||||
Task<IReadOnlyList<IInstanceClient>> Read(CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of all <see cref="Instance"/>s the user can view</returns>
|
||||
Task<IReadOnlyList<Instance>> List(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create an <paramref name="instance"/>
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="Instance"/> to create. <see cref="Instance.Id"/> will be ignored</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see cref="IInstanceClient"/> for the created <see cref="Instance"/></returns>
|
||||
Task<IInstanceClient> Create(Instance instance, CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the created <see cref="Instance"/></returns>
|
||||
Task<Instance> Create(Instance instance, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Relocates, renamed, and/or on/offlines an <paramref name="instance"/>
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="Instance"/> to update</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Update(Instance instance, CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the updated <see cref="Instance"/></returns>
|
||||
Task<Instance> Update(Instance instance, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an <paramref name="instance"/>
|
||||
@@ -41,5 +41,12 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Delete(Instance instance, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create an <see cref="IInstanceClient"/> for a given <see cref="Instance"/>
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="Instance"/> to create an <see cref="IInstanceClient"/> for</param>
|
||||
/// <returns>A new <see cref="IInstanceClient"/></returns>
|
||||
IInstanceClient CreateClient(Instance instance);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Api"/> client that has a <typeparamref name="TRights"/> bitset
|
||||
/// </summary>
|
||||
/// <typeparam name="TRights">The <see cref="Api.Rights"/> for the <see cref="IRightsClient{TRights}"/></typeparam>
|
||||
public interface IRightsClient<TRights>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <typeparamref name="TRights"/> for the <see cref="IRightsClient{TRights}"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <typeparamref name="TRights"/> for the <see cref="IRightsClient{TRights}"/></returns>
|
||||
Task<TRights> Rights(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Client.Components;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
@@ -11,24 +11,14 @@ namespace Tgstation.Server.Client
|
||||
public interface IServerClient : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="System.Version"/> of the <see cref="IServerClient"/>
|
||||
/// The <see cref="Token"/> being used to access the server
|
||||
/// </summary>
|
||||
Version Version { get; }
|
||||
Token Token { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The connection timeout in milliseconds. Defaults to 10000
|
||||
/// The connection timeout
|
||||
/// </summary>
|
||||
int Timeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How long to return initially cached models for in seconds. Defaults to 60
|
||||
/// </summary>
|
||||
int CacheExpiry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The requery rate for job updates in milliseconds. Defaults to 5000
|
||||
/// </summary>
|
||||
int RequeryRate { get; set; }
|
||||
TimeSpan Timeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="IInstanceManagerClient"/>
|
||||
@@ -41,15 +31,13 @@ namespace Tgstation.Server.Client
|
||||
IAdministrationClient Administration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// These generally shouldn't be used in favor of the <see cref="Task"/> based polling and <see cref="CancellationToken"/>s other clients use. However, this is the only way to access jobs the client didn't start in it's current session
|
||||
/// Access the <see cref="IUsersClient"/>
|
||||
/// </summary>
|
||||
IJobsClient Jobs { get; }
|
||||
IUsersClient Users { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="System.Version"/> of the connected server
|
||||
/// The <see cref="System.Version"/> of the <see cref="IServerClient"/>
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="System.Version"/> of the connected server</returns>
|
||||
/// <remarks>Note that if the <see cref="System.Version.Build"/> differs from the <see cref="Version"/>'s API functionality will most likely be compromised</remarks>
|
||||
Task<Version> GetServerVersion(CancellationToken cancellationToken);
|
||||
Task<Version> Version(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Threading;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
@@ -11,24 +13,21 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// Create a <see cref="IServerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="hostname">The URL to access tgstation-server at</param>
|
||||
/// <param name="host">The URL to access TGS</param>
|
||||
/// <param name="username">The username to for the <see cref="IServerClient"/></param>
|
||||
/// <param name="password">The password for the <see cref="IServerClient"/></param>
|
||||
/// <param name="timeout">The initial timeout for the connection in milliseconds</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <param name="timeout">The <see cref="TimeSpan"/> representing timeout for the connection</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="IServerClient"/></returns>
|
||||
/// <exception cref="System.UnauthorizedAccessException">If the <paramref name="username"/> and/or <paramref name="password"/> is invalid</exception>
|
||||
Task<IServerClient> CreateServerClient(string hostname, string username, string password, int timeout, CancellationToken cancellationToken);
|
||||
Task<IServerClient> CreateServerClient(Uri host, string username, string password, TimeSpan timeout = default, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="IServerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="hostname">The URL to access tgstation-server at</param>
|
||||
/// <param name="token">The <see cref="Api.Models.Token.Bearer"/> to access the API with</param>
|
||||
/// <param name="timeout">The initial timeout for the connection</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="IServerClient"/></returns>
|
||||
/// <exception cref="System.UnauthorizedAccessException">If the <paramref name="token"/> invalid</exception>
|
||||
Task<IServerClient> CreateServerClient(string hostname, string token, int timeout, CancellationToken cancellationToken);
|
||||
/// <param name="host">The URL to access TGS</param>
|
||||
/// <param name="token">The <see cref="Token"/> to access the API with</param>
|
||||
/// <param name="timeout">The <see cref="TimeSpan"/> representing timeout for the connection</param>
|
||||
/// <returns>A new <see cref="IServerClient"/></returns>
|
||||
IServerClient CreateServerClient(Uri host, Token token, TimeSpan timeout = default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing <see cref="User"/>s
|
||||
/// </summary>
|
||||
public interface IUsersClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Read the current user's information and general rights
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns></returns>
|
||||
Task<User> Read(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <paramref name="user"/>
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="UserUpdate"/> used to create the new <see cref="User"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>The new <see cref="User"/></returns>
|
||||
Task<User> Create(UserUpdate user, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Update a <paramref name="user"/>
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="UserUpdate"/> used to update the <see cref="User"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>The updated <see cref="User"/></returns>
|
||||
Task<User> Update(UserUpdate user, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Client.Components;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class InstanceManagerClient : IInstanceManagerClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="InstanceManagerClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// Map of already created <see cref="IInstanceClient"/>s
|
||||
/// </summary>
|
||||
readonly Dictionary<long, IInstanceClient> cachedClients;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InstanceManagerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient"></param>
|
||||
public InstanceManagerClient(IApiClient apiClient)
|
||||
{
|
||||
this.apiClient = apiClient;
|
||||
|
||||
cachedClients = new Dictionary<long, IInstanceClient>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Instance> Create(Instance instance, CancellationToken cancellationToken) => apiClient.Create<Instance, Instance>(Routes.InstanceManager, instance, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(Instance instance, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.InstanceManager, instance.Id), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<Instance>> List(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<Instance>>(Routes.List(Routes.InstanceManager), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Instance> Update(Instance instance, CancellationToken cancellationToken) => apiClient.Update<Instance, Instance>(Routes.InstanceManager, instance, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IInstanceClient CreateClient(Instance instance)
|
||||
{
|
||||
if (!cachedClients.TryGetValue(instance.Id, out var client))
|
||||
{
|
||||
client = new InstanceClient(apiClient, instance);
|
||||
cachedClients.Add(instance.Id, client);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the client attempts to perform an action they do not have the rights for
|
||||
/// </summary>
|
||||
public sealed class InsufficientPermissionsException : ClientException
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InsufficientPermissionsException"/>
|
||||
/// </summary>
|
||||
public InsufficientPermissionsException() : base(new ErrorMessage
|
||||
{
|
||||
Message = "The credentials provided do not have sufficient rights to make this request!",
|
||||
SeverApiVersion = null
|
||||
}, HttpStatusCode.Forbidden)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InsufficientPermissionsException"/> with a <paramref name="message"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
public InsufficientPermissionsException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InsufficientPermissionsException"/> with a <paramref name="message"/> and <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public InsufficientPermissionsException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the client tries to use a currently unsupported API
|
||||
/// </summary>
|
||||
public sealed class MethodNotSupportedException : ClientException
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct an <see cref="MethodNotSupportedException"/>
|
||||
/// </summary>
|
||||
public MethodNotSupportedException() : base(new ErrorMessage
|
||||
{
|
||||
Message = "This method is not currently supported!",
|
||||
SeverApiVersion = null
|
||||
}, HttpStatusCode.NotImplemented)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="MethodNotSupportedException"/> with a <paramref name="message"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
public MethodNotSupportedException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="MethodNotSupportedException"/> with a <paramref name="message"/> and <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public MethodNotSupportedException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when a GitHub rate limit occurs
|
||||
/// </summary>
|
||||
public sealed class RateLimitException : ClientException
|
||||
{
|
||||
DateTimeOffset RetryAfter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="RateLimitException"/>
|
||||
/// </summary>
|
||||
public RateLimitException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="RateLimitException"/> with a <paramref name="secondsString"/>
|
||||
/// </summary>
|
||||
/// <param name="secondsString">The message for the <see cref="Exception"/></param>
|
||||
public RateLimitException(string secondsString) : base(new ErrorMessage
|
||||
{
|
||||
Message = "GitHub rate limit reached!",
|
||||
SeverApiVersion = null
|
||||
}, (HttpStatusCode)429)
|
||||
{
|
||||
if (Int32.TryParse(secondsString, out var seconds) && seconds >= 0)
|
||||
RetryAfter = DateTimeOffset.Now.AddSeconds(seconds);
|
||||
else
|
||||
RetryAfter = DateTimeOffset.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="RateLimitException"/> with a <paramref name="message"/> and <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public RateLimitException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the client provides invalid credentials
|
||||
/// </summary>
|
||||
public sealed class RequestTimeoutException : ClientException
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct an <see cref="RequestTimeoutException"/>
|
||||
/// </summary>
|
||||
public RequestTimeoutException() : base(new ErrorMessage
|
||||
{
|
||||
Message = "The request timed out!",
|
||||
SeverApiVersion = null
|
||||
}, HttpStatusCode.RequestTimeout)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="RequestTimeoutException"/> with a <paramref name="message"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
public RequestTimeoutException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="RequestTimeoutException"/> with a <paramref name="message"/> and <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public RequestTimeoutException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class ServerClient : IServerClient
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Token Token { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public TimeSpan Timeout
|
||||
{
|
||||
get => apiClient.Timeout;
|
||||
set => apiClient.Timeout = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IInstanceManagerClient Instances { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAdministrationClient Administration { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IUsersClient Users { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="ServerClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ServerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="token">The value of <see cref="Token"/></param>
|
||||
public ServerClient(IApiClient apiClient, Token token)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
Token = token ?? throw new ArgumentNullException(nameof(token));
|
||||
|
||||
if (Token.Bearer != apiClient.Headers.Token)
|
||||
throw new ArgumentOutOfRangeException(nameof(token), token, "Provided token does not match apiClient headers!");
|
||||
|
||||
Instances = new InstanceManagerClient(apiClient);
|
||||
Users = new UsersClient(apiClient);
|
||||
Administration = new AdministrationClient(apiClient);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => apiClient.Dispose();
|
||||
/// <inheritdoc />
|
||||
public Task<Version> Version(CancellationToken cancellationToken) => apiClient.Read<Version>(Routes.Root, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,67 @@
|
||||
using System;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class ServerClientFactory : IServerClientFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClientFactory"/> for the <see cref="ServerClientFactory"/>
|
||||
/// </summary>
|
||||
static readonly IApiClientFactory apiClientFactory = new ApiClientFactory();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ProductHeaderValue"/> for the <see cref="ServerClientFactory"/>
|
||||
/// </summary>
|
||||
readonly ProductHeaderValue productHeaderValue;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ServerClientFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="productHeaderValue">The value of <see cref="productHeaderValue"/></param>
|
||||
public ServerClientFactory(ProductHeaderValue productHeaderValue)
|
||||
{
|
||||
this.productHeaderValue = productHeaderValue ?? throw new ArgumentNullException(nameof(productHeaderValue));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IServerClient> CreateServerClient(string hostname, string username, string password, int timeout, CancellationToken cancellationToken) => throw new NotImplementedException();
|
||||
public async Task<IServerClient> CreateServerClient(Uri host, string username, string password, TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (host == null)
|
||||
throw new ArgumentNullException(nameof(host));
|
||||
if (username == null)
|
||||
throw new ArgumentNullException(nameof(username));
|
||||
if (password == null)
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
if (timeout == null)
|
||||
throw new ArgumentNullException(nameof(timeout));
|
||||
|
||||
Token token;
|
||||
using (var api = apiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, username, password)))
|
||||
{
|
||||
if (timeout != default)
|
||||
api.Timeout = timeout;
|
||||
token = await api.Update<Token>(Routes.Root, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
return CreateServerClient(host, token, timeout);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IServerClient> CreateServerClient(string hostname, string token, int timeout, CancellationToken cancellationToken) => throw new NotImplementedException();
|
||||
public IServerClient CreateServerClient(Uri host, Token token, TimeSpan timeout)
|
||||
{
|
||||
if (host == null)
|
||||
throw new ArgumentNullException(nameof(host));
|
||||
if (token == null)
|
||||
throw new ArgumentNullException(nameof(token));
|
||||
var result = new ServerClient(apiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer)), token);
|
||||
if (timeout != default)
|
||||
result.Timeout = timeout;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when an error occurs in the server
|
||||
/// </summary>
|
||||
public sealed class ServerErrorException : ClientException
|
||||
{
|
||||
/// <summary>
|
||||
/// The raw HTML of the error
|
||||
/// </summary>
|
||||
public string Html { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ServerErrorException"/>
|
||||
/// </summary>
|
||||
public ServerErrorException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ServerErrorException"/> with <paramref name="html"/>
|
||||
/// </summary>
|
||||
/// <param name="html">The raw HTML response of the <see cref="ServerErrorException"/></param>
|
||||
public ServerErrorException(string html) : base(null, HttpStatusCode.InternalServerError)
|
||||
{
|
||||
Html = html;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ServerErrorException"/> with a <paramref name="message"/> and <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public ServerErrorException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,10 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Tgstation.Server.Api\Tgstation.Server.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the client provides invalid credentials
|
||||
/// </summary>
|
||||
public sealed class UnauthorizedException : ClientException
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct an <see cref="UnauthorizedException"/>
|
||||
/// </summary>
|
||||
public UnauthorizedException() : base(new ErrorMessage
|
||||
{
|
||||
Message = "Invalid credentials!",
|
||||
SeverApiVersion = null
|
||||
}, HttpStatusCode.Unauthorized)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="UnauthorizedException"/> with a <paramref name="message"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
public UnauthorizedException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="UnauthorizedException"/> with a <paramref name="message"/> and <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/></param>
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public UnauthorizedException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class UsersClient : IUsersClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="apiClient"/> for the <see cref="UsersClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="UsersClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
public UsersClient(IApiClient apiClient)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<User> Create(UserUpdate user, CancellationToken cancellationToken) => apiClient.Create<UserUpdate, User>(Routes.User, user, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<User> Read(CancellationToken cancellationToken) => apiClient.Read<User>(Routes.User, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<User> Update(UserUpdate user, CancellationToken cancellationToken) => apiClient.Update<UserUpdate, User>(Routes.User, user, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
readonly Dictionary<string, ICommand> builtinCommands;
|
||||
|
||||
/// <summary>
|
||||
/// Map of <see cref="IProvider"/>s in use, keyed by <see cref="ChatSettings.Id"/>
|
||||
/// Map of <see cref="IProvider"/>s in use, keyed by <see cref="ChatBot.Id"/>
|
||||
/// </summary>
|
||||
readonly Dictionary<long, IProvider> providers;
|
||||
|
||||
@@ -64,9 +64,9 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
readonly CancellationTokenSource handlerCts;
|
||||
|
||||
/// <summary>
|
||||
/// The initial <see cref="Models.ChatSettings"/> for the <see cref="Chat"/>
|
||||
/// The initial <see cref="Models.ChatBot"/> for the <see cref="Chat"/>
|
||||
/// </summary>
|
||||
readonly List<Models.ChatSettings> initialChatSettings;
|
||||
readonly List<Models.ChatBot> initialChatBots;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICustomCommandHandler"/> for the <see cref="ChangeChannels(long, IEnumerable{Api.Models.ChatChannel}, CancellationToken)"/>
|
||||
@@ -79,7 +79,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
Task chatHandler;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TaskCompletionSource{TResult}"/> that completes when <see cref="ChatSettings"/> change
|
||||
/// The <see cref="TaskCompletionSource{TResult}"/> that completes when <see cref="ChatBot"/>s change
|
||||
/// </summary>
|
||||
TaskCompletionSource<object> connectionsUpdated;
|
||||
|
||||
@@ -100,14 +100,14 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="commandFactory">The value of <see cref="commandFactory"/></param>
|
||||
/// <param name="initialChatSettings">The <see cref="IEnumerable{T}"/> used to populate <see cref="initialChatSettings"/></param>
|
||||
public Chat(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, ILogger<Chat> logger, IEnumerable<Models.ChatSettings> initialChatSettings)
|
||||
/// <param name="initialChatBots">The <see cref="IEnumerable{T}"/> used to populate <see cref="initialChatBots"/></param>
|
||||
public Chat(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, ILogger<Chat> logger, IEnumerable<Models.ChatBot> initialChatBots)
|
||||
{
|
||||
this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.initialChatSettings = initialChatSettings?.ToList() ?? throw new ArgumentNullException(nameof(initialChatSettings));
|
||||
this.initialChatBots = initialChatBots?.ToList() ?? throw new ArgumentNullException(nameof(initialChatBots));
|
||||
|
||||
builtinCommands = new Dictionary<string, ICommand>();
|
||||
providers = new Dictionary<long, IProvider>();
|
||||
@@ -129,7 +129,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <summary>
|
||||
/// Remove a <see cref="IProvider"/> from <see cref="providers"/> and <see cref="mappedChannels"/> optionally updating the <see cref="trackingContexts"/> as well
|
||||
/// </summary>
|
||||
/// <param name="connectionId">The <see cref="ChatSettings.Id"/> of the <see cref="IProvider"/> to delete</param>
|
||||
/// <param name="connectionId">The <see cref="ChatBot.Id"/> of the <see cref="IProvider"/> to delete</param>
|
||||
/// <param name="updateTrackings">If <see cref="trackingContexts"/> should be update</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IProvider"/> being removed if it exists, <see langword="false"/> otherwise</returns>
|
||||
@@ -164,8 +164,6 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task ProcessMessage(IProvider provider, Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("Chat message: {0}. User (Note unconverted provider Id): {1}", message.Content, JsonConvert.SerializeObject(message.User));
|
||||
|
||||
//map the channel if it's private and we haven't seen it
|
||||
if (message.User.Channel.IsPrivate)
|
||||
lock (providers)
|
||||
@@ -205,6 +203,8 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
//no mention
|
||||
return;
|
||||
|
||||
logger.LogTrace("Chat command: {0}. User (True provider Id): {1}", message.Content, JsonConvert.SerializeObject(message.User));
|
||||
|
||||
if (addressed)
|
||||
splits.RemoveAt(0);
|
||||
|
||||
@@ -375,7 +375,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ChangeSettings(ChatSettings newSettings, CancellationToken cancellationToken)
|
||||
public async Task ChangeSettings(ChatBot newSettings, CancellationToken cancellationToken)
|
||||
{
|
||||
if (newSettings == null)
|
||||
throw new ArgumentNullException(nameof(newSettings));
|
||||
@@ -478,9 +478,9 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
foreach (var I in commandFactory.GenerateCommands())
|
||||
builtinCommands.Add(I.Name.ToUpperInvariant(), I);
|
||||
await Task.WhenAll(initialChatSettings.Select(x => ChangeSettings(x, cancellationToken))).ConfigureAwait(false);
|
||||
await Task.WhenAll(initialChatBots.Select(x => ChangeSettings(x, cancellationToken))).ConfigureAwait(false);
|
||||
await Task.WhenAll(providers.Select(x => x.Value).Select(x => x.Connect(cancellationToken))).ConfigureAwait(false);
|
||||
await Task.WhenAll(initialChatSettings.Select(x => ChangeChannels(x.Id, x.Channels, cancellationToken))).ConfigureAwait(false);
|
||||
await Task.WhenAll(initialChatBots.Select(x => ChangeChannels(x.Id, x.Channels, cancellationToken))).ConfigureAwait(false);
|
||||
chatHandler = MonitorMessages(handlerCts.Token);
|
||||
started = true;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,6 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IChat CreateChat(IEnumerable<Models.ChatSettings> initialChatSettings) => new Chat(providerFactory, ioManager, commandFactory, loggerFactory.CreateLogger<Chat>(), initialChatSettings);
|
||||
public IChat CreateChat(IEnumerable<Models.ChatBot> initialChatBots) => new Chat(providerFactory, ioManager, commandFactory, loggerFactory.CreateLogger<Chat>(), initialChatBots);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
public interface IChat : IHostedService, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// If a given set of <see cref="ChatSettings"/> is connected
|
||||
/// If a given set of <see cref="ChatBot"/> is connected
|
||||
/// </summary>
|
||||
/// <param name="connectionId">The <see cref="ChatSettings.Id"/> of the connection</param>
|
||||
/// <param name="connectionId">The <see cref="ChatBot.Id"/> of the connection</param>
|
||||
/// <returns><see langword="true"/> if it is connected, <see langword="false"/> otherwise</returns>
|
||||
bool Connected(long connectionId);
|
||||
|
||||
@@ -26,17 +26,17 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
void RegisterCommandHandler(ICustomCommandHandler customCommandHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Change chat settings. If the <see cref="ChatSettings.Id"/> is not currently in use, a new connection will be made instead
|
||||
/// Change chat settings. If the <see cref="ChatBot.Id"/> is not currently in use, a new connection will be made instead
|
||||
/// </summary>
|
||||
/// <param name="newSettings">The new <see cref="ChatSettings"/></param>
|
||||
/// <param name="newSettings">The new <see cref="ChatBot"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation. Will complete immediately if the <see cref="ChatSettings.Enabled"/> property of <paramref name="newSettings"/> is <see langword="false"/></returns>
|
||||
Task ChangeSettings(ChatSettings newSettings, CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task"/> representing the running operation. Will complete immediately if the <see cref="ChatBot.Enabled"/> property of <paramref name="newSettings"/> is <see langword="false"/></returns>
|
||||
Task ChangeSettings(ChatBot newSettings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects and deletes a given connection
|
||||
/// </summary>
|
||||
/// <param name="connectionId">The <see cref="ChatSettings.Id"/> of the connection</param>
|
||||
/// <param name="connectionId">The <see cref="ChatBot.Id"/> of the connection</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task DeleteConnection(long connectionId, CancellationToken cancellationToken);
|
||||
@@ -44,7 +44,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <summary>
|
||||
/// Change chat channels
|
||||
/// </summary>
|
||||
/// <param name="connectionId">The <see cref="ChatSettings.Id"/> of the connection</param>
|
||||
/// <param name="connectionId">The <see cref="ChatBot.Id"/> of the connection</param>
|
||||
/// <param name="newChannels">An <see cref="IEnumerable{T}"/> of the new list of <see cref="Api.Models.ChatChannel"/>s</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
|
||||
@@ -10,8 +10,8 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <summary>
|
||||
/// Create a <see cref="IChat"/>
|
||||
/// </summary>
|
||||
/// <param name="initialChatSettings">The initial <see cref="Models.ChatSettings"/> for the <see cref="IChat"/></param>
|
||||
/// <param name="initialChatBots">The initial <see cref="Models.ChatBot"/> for the <see cref="IChat"/></param>
|
||||
/// <returns>A new <see cref="IChat"/></returns>
|
||||
IChat CreateChat(IEnumerable<Models.ChatSettings> initialChatSettings);
|
||||
IChat CreateChat(IEnumerable<Models.ChatBot> initialChatBots);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <summary>
|
||||
/// Create a <see cref="IProvider"/>
|
||||
/// </summary>
|
||||
/// <param name="settings">The <see cref="ChatSettings"/> for the new provider</param>
|
||||
/// <param name="settings">The <see cref="ChatBot"/> containing settings for the new provider</param>
|
||||
/// <returns>A new <see cref="IProvider"/></returns>
|
||||
IProvider CreateProvider(ChatSettings settings);
|
||||
IProvider CreateProvider(ChatBot settings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IProvider CreateProvider(Api.Models.Internal.ChatSettings settings)
|
||||
public IProvider CreateProvider(Api.Models.Internal.ChatBot settings)
|
||||
{
|
||||
if (settings == null)
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// The <see cref="ILogger"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly ILogger<DreamMaker> logger;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Construct <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
@@ -115,15 +115,16 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// <param name="securityLevel">The <see cref="DreamDaemonSecurity"/> level to use to validate the API</param>
|
||||
/// <param name="job">The <see cref="Models.CompileJob"/> for the operation</param>
|
||||
/// <param name="byondLock">The current <see cref="IByondExecutableLock"/></param>
|
||||
/// <param name="portToUse">The port to use for API validation</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if the DMAPI was successfully validated, <see langword="false"/> otherwise</returns>
|
||||
async Task<bool> VerifyApi(uint timeout, DreamDaemonSecurity securityLevel, Models.CompileJob job, IByondExecutableLock byondLock, CancellationToken cancellationToken)
|
||||
async Task<bool> VerifyApi(uint timeout, DreamDaemonSecurity securityLevel, Models.CompileJob job, IByondExecutableLock byondLock, ushort portToUse, CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("Verifying DMAPI...");
|
||||
logger.LogTrace("Verifying DMAPI...");
|
||||
var launchParameters = new DreamDaemonLaunchParameters
|
||||
{
|
||||
AllowWebClient = false,
|
||||
PrimaryPort = 0, //pick any port
|
||||
PrimaryPort = portToUse,
|
||||
SecurityLevel = securityLevel, //all it needs to read the file and exit
|
||||
StartupTimeout = timeout
|
||||
};
|
||||
@@ -229,8 +230,14 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Models.CompileJob> Compile(Models.RevisionInformation revisionInformation, string projectName, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken)
|
||||
public async Task<Models.CompileJob> Compile(Models.RevisionInformation revisionInformation, DreamMakerSettings dreamMakerSettings, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken)
|
||||
{
|
||||
if (revisionInformation == null)
|
||||
throw new ArgumentNullException(nameof(revisionInformation));
|
||||
|
||||
if (dreamMakerSettings == null)
|
||||
throw new ArgumentNullException(nameof(dreamMakerSettings));
|
||||
|
||||
if (repository == null)
|
||||
throw new ArgumentNullException(nameof(repository));
|
||||
|
||||
@@ -242,7 +249,7 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
var job = new Models.CompileJob
|
||||
{
|
||||
DirectoryName = Guid.NewGuid(),
|
||||
DmeName = projectName,
|
||||
DmeName = dreamMakerSettings.ProjectName,
|
||||
RevisionInformation = revisionInformation
|
||||
};
|
||||
|
||||
@@ -260,12 +267,15 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
Status = CompilerStatus.Copying;
|
||||
}
|
||||
|
||||
var commitInsert = revisionInformation.CommitSha;
|
||||
var remoteCommitInsert = String.Empty;
|
||||
if (commitInsert == revisionInformation.OriginCommitSha)
|
||||
commitInsert = String.Format(CultureInfo.InvariantCulture, "^{0}", commitInsert.Substring(0, 7));
|
||||
var commitInsert = revisionInformation.CommitSha.Substring(0, 7);
|
||||
string remoteCommitInsert;
|
||||
if (revisionInformation.CommitSha == revisionInformation.OriginCommitSha)
|
||||
{
|
||||
commitInsert = String.Format(CultureInfo.InvariantCulture, "^{0}", commitInsert);
|
||||
remoteCommitInsert = String.Empty;
|
||||
}
|
||||
else
|
||||
remoteCommitInsert = String.Format(CultureInfo.InvariantCulture, ". Remote commit: ^{0}", revisionInformation.OriginCommitSha);
|
||||
remoteCommitInsert = String.Format(CultureInfo.InvariantCulture, ". Remote commit: ^{0}", revisionInformation.OriginCommitSha.Substring(0, 7));
|
||||
|
||||
var testmergeInsert = revisionInformation.ActiveTestMerges.Count == 0 ? String.Empty : String.Format(CultureInfo.InvariantCulture, " (Test Merges: {0})",
|
||||
String.Join(", ", revisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x =>
|
||||
@@ -286,11 +296,11 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
|
||||
var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), BDirectoryName);
|
||||
|
||||
async Task CleanupFailedCompile()
|
||||
async Task CleanupFailedCompile(bool announce)
|
||||
{
|
||||
logger.LogTrace("Cleaning compile directory...");
|
||||
Status = CompilerStatus.Cleanup;
|
||||
var chatTask = chat.SendUpdateMessage("DM: Deploy failed!", cancellationToken);
|
||||
var chatTask = announce ? chat.SendUpdateMessage("Deploy failed!", cancellationToken) : Task.CompletedTask;
|
||||
try
|
||||
{
|
||||
await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false);
|
||||
@@ -314,7 +324,8 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
|
||||
Status = CompilerStatus.PreCompile;
|
||||
|
||||
await eventConsumer.HandleEvent(EventType.CompileStart, new List<string> { ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName)), repoOrigin }, cancellationToken).ConfigureAwait(false);
|
||||
var resolvedGameDirectory = ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName));
|
||||
await eventConsumer.HandleEvent(EventType.CompileStart, new List<string> { resolvedGameDirectory, repoOrigin }, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
Status = CompilerStatus.Modifying;
|
||||
|
||||
@@ -339,53 +350,51 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
Status = CompilerStatus.Compiling;
|
||||
|
||||
//run compiler, verify api
|
||||
bool ddVerified;
|
||||
job.ByondVersion = byondLock.Version.ToString();
|
||||
|
||||
await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
Status = CompilerStatus.Verifying;
|
||||
if (job.ExitCode == 0)
|
||||
{
|
||||
Status = CompilerStatus.Verifying;
|
||||
|
||||
ddVerified = job.ExitCode == 0 && await VerifyApi(apiValidateTimeout, securityLevel, job, byondLock, cancellationToken).ConfigureAwait(false);
|
||||
job.DMApiValidated = await VerifyApi(apiValidateTimeout, securityLevel, job, byondLock, dreamMakerSettings.ApiValidationPort.Value, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!ddVerified)
|
||||
if (job.DMApiValidated != true)
|
||||
{
|
||||
//server never validated or compile failed
|
||||
await CleanupFailedCompile().ConfigureAwait(false);
|
||||
await eventConsumer.HandleEvent(EventType.CompileFailure, new List<string> { job.ExitCode == 0 ? "1" : "0" }, cancellationToken).ConfigureAwait(false);
|
||||
await eventConsumer.HandleEvent(EventType.CompileFailure, new List<string> { resolvedGameDirectory, job.ExitCode == 0 ? "1" : "0" }, cancellationToken).ConfigureAwait(false);
|
||||
throw new Exception(job.ExitCode == 0 ? "Validation of the TGS api failed!" : "DM exited with a non-zero code!");
|
||||
}
|
||||
else
|
||||
{
|
||||
job.DMApiValidated = true;
|
||||
|
||||
logger.LogTrace("Running post compile event...");
|
||||
Status = CompilerStatus.PostCompile;
|
||||
await eventConsumer.HandleEvent(EventType.CompileComplete, new List<string> { ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName)) }, cancellationToken).ConfigureAwait(false);
|
||||
logger.LogTrace("Running post compile event...");
|
||||
Status = CompilerStatus.PostCompile;
|
||||
await eventConsumer.HandleEvent(EventType.CompileComplete, new List<string> { ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName)) }, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
logger.LogTrace("Duplicating compiled game...");
|
||||
Status = CompilerStatus.Duplicating;
|
||||
logger.LogTrace("Duplicating compiled game...");
|
||||
Status = CompilerStatus.Duplicating;
|
||||
|
||||
//duplicate the dmb et al
|
||||
await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false);
|
||||
//duplicate the dmb et al
|
||||
await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
logger.LogTrace("Applying static game file symlinks...");
|
||||
Status = CompilerStatus.Symlinking;
|
||||
logger.LogTrace("Applying static game file symlinks...");
|
||||
Status = CompilerStatus.Symlinking;
|
||||
|
||||
//symlink in the static data
|
||||
var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken);
|
||||
var symBTask = configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken);
|
||||
//symlink in the static data
|
||||
var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken);
|
||||
var symBTask = configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken);
|
||||
|
||||
await Task.WhenAll(symATask, symBTask).ConfigureAwait(false);
|
||||
await Task.WhenAll(symATask, symBTask).ConfigureAwait(false);
|
||||
|
||||
await chat.SendUpdateMessage("Deployment complete! Changes will be applied on next server reboot.", cancellationToken).ConfigureAwait(false);
|
||||
await chat.SendUpdateMessage("Deployment complete! Changes will be applied on next server reboot.", cancellationToken).ConfigureAwait(false);
|
||||
|
||||
logger.LogDebug("Compile complete!");
|
||||
}
|
||||
logger.LogDebug("Compile complete!");
|
||||
return job;
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
await CleanupFailedCompile().ConfigureAwait(false);
|
||||
await CleanupFailedCompile(!(e is OperationCanceledException)).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Compiler
|
||||
@@ -19,12 +20,12 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// Starts a compile
|
||||
/// </summary>
|
||||
/// <param name="revisionInformation">The <see cref="Models.RevisionInformation"/> being compiled from the <paramref name="repository"/></param>
|
||||
/// <param name="projectName">The optional name of the .dme to compile without the extension if not pre</param>
|
||||
/// <param name="dreamMakerSettings">The <see cref="DreamMakerSettings"/> for the compile</param>
|
||||
/// <param name="securityLevel">The <see cref="DreamDaemonSecurity"/> level allowed for API validation</param>
|
||||
/// <param name="apiValidateTimeout">The time in seconds to wait while validating the API</param>
|
||||
/// <param name="repository">The <see cref="IRepository"/> to copy from</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the partially populated <see cref="CompileJob"/> for the operation. In particular, note the <see cref="CompileJob.RevisionInformation"/> field will only have it's <see cref="Api.Models.Internal.RevisionInformation.CommitSha"/> field populated</returns>
|
||||
Task<Models.CompileJob> Compile(Models.RevisionInformation revisionInformation, string projectName, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the partially populated <see cref="Models.CompileJob"/> for the operation. In particular, note the <see cref="Models.CompileJob.RevisionInformation"/> field will only have it's <see cref="Api.Models.Internal.RevisionInformation.CommitSha"/> field populated</returns>
|
||||
Task<Models.CompileJob> Compile(Models.RevisionInformation revisionInformation, DreamMakerSettings dreamMakerSettings, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@
|
||||
/// </summary>
|
||||
CompileCancelled = 9,
|
||||
/// <summary>
|
||||
/// Parameters: "1" if compile succeeded and api validation failed, "0" otherwise
|
||||
/// Parameters: Game directory path, "1" if compile succeeded and api validation failed, "0" otherwise
|
||||
/// </summary>
|
||||
CompileFailure = 10,
|
||||
/// <summary>
|
||||
|
||||
@@ -133,8 +133,8 @@ namespace Tgstation.Server.Host.Components
|
||||
StartupTimeout = x.StartupTimeout,
|
||||
SecurityLevel = x.SecurityLevel
|
||||
}).FirstAsync(cancellationToken);
|
||||
var projectNameTask = instanceQuery.Select(x => x.DreamMakerSettings.ProjectName).FirstOrDefaultAsync(cancellationToken);
|
||||
var repositorySettingsTask = instanceQuery.Select(x => x.RepositorySettings).FirstOrDefaultAsync(cancellationToken);
|
||||
var dmSettingsTask = instanceQuery.Select(x => x.DreamMakerSettings).FirstAsync(cancellationToken);
|
||||
var repositorySettingsTask = instanceQuery.Select(x => x.RepositorySettings).FirstAsync(cancellationToken);
|
||||
|
||||
using (var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
@@ -169,7 +169,7 @@ namespace Tgstation.Server.Host.Components
|
||||
await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, shouldSyncTracked, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//finish other queries
|
||||
var projectName = await projectNameTask.ConfigureAwait(false);
|
||||
var dmSettings = await dmSettingsTask.ConfigureAwait(false);
|
||||
var ddSettings = await ddSettingsTask.ConfigureAwait(false);
|
||||
var revInfo = await revInfoTask.ConfigureAwait(false);
|
||||
|
||||
@@ -190,7 +190,7 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
|
||||
//finally start compile
|
||||
job = await DreamMaker.Compile(revInfo, projectName, ddSettings.SecurityLevel.Value, ddSettings.StartupTimeout.Value, repo, cancellationToken).ConfigureAwait(false);
|
||||
job = await DreamMaker.Compile(revInfo, dmSettings, ddSettings.SecurityLevel.Value, ddSettings.StartupTimeout.Value, repo, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
db.CompileJobs.Add(job);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -96,7 +97,10 @@ namespace Tgstation.Server.Host.Components.Interop
|
||||
CommCommand command;
|
||||
try
|
||||
{
|
||||
command = JsonConvert.DeserializeObject<CommCommand>(file);
|
||||
command = new CommCommand
|
||||
{
|
||||
Parameters = JsonConvert.DeserializeObject<IReadOnlyDictionary<string, string>>(file)
|
||||
};
|
||||
}
|
||||
catch (JsonSerializationException ex)
|
||||
{
|
||||
|
||||
@@ -9,6 +9,11 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// </summary>
|
||||
public interface IRepositoryManager : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// If a <see cref="CloneRepository(Uri, string, string, string, Action{int}, CancellationToken)"/> operation is in progress
|
||||
/// </summary>
|
||||
bool CloneInProgress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to load the <see cref="IRepository"/> from the default location
|
||||
/// </summary>
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <inheritdoc />
|
||||
sealed class RepositoryManager : IRepositoryManager
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool CloneInProgress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="RepositoryManager"/>
|
||||
/// </summary>
|
||||
@@ -51,56 +54,72 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <inheritdoc />
|
||||
public async Task<IRepository> CloneRepository(Uri url, string initialBranch, string username, string password, Action<int> progressReporter, CancellationToken cancellationToken)
|
||||
{
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
if (!await ioManager.DirectoryExists(".", cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
string path = null;
|
||||
try
|
||||
{
|
||||
path = LibGit2Sharp.Repository.Clone(url.ToString(), ioManager.ResolvePath("."), new CloneOptions
|
||||
{
|
||||
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
|
||||
OnTransferProgress = (a) =>
|
||||
{
|
||||
var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
|
||||
progressReporter((int)percentage);
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
RecurseSubmodules = true,
|
||||
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested,
|
||||
BranchName = initialBranch,
|
||||
CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials
|
||||
{
|
||||
Username = username,
|
||||
Password = password
|
||||
} : new DefaultCredentials()
|
||||
});
|
||||
}
|
||||
catch (UserCancelledException) { }
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (CloneInProgress)
|
||||
throw new InvalidOperationException("The repository is already being cloned!");
|
||||
CloneInProgress = true;
|
||||
}
|
||||
try
|
||||
{
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
if (!await ioManager.DirectoryExists(".", cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
await ioManager.DeleteDirectory(".", default).ConfigureAwait(false);
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
string path = null;
|
||||
try
|
||||
{
|
||||
path = LibGit2Sharp.Repository.Clone(url.ToString(), ioManager.ResolvePath("."), new CloneOptions
|
||||
{
|
||||
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
|
||||
OnTransferProgress = (a) =>
|
||||
{
|
||||
var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
|
||||
progressReporter((int)percentage);
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
RecurseSubmodules = true,
|
||||
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested,
|
||||
BranchName = initialBranch,
|
||||
CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials
|
||||
{
|
||||
Username = username,
|
||||
Password = password
|
||||
} : new DefaultCredentials()
|
||||
});
|
||||
}
|
||||
catch (UserCancelledException) { }
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
throw;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
await ioManager.DeleteDirectory(".", default).ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
throw;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloneInProgress = false;
|
||||
}
|
||||
return await LoadRepository(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IRepository> LoadRepository(CancellationToken cancellationToken)
|
||||
{
|
||||
lock(this)
|
||||
if (CloneInProgress)
|
||||
throw new InvalidOperationException("The repository is being cloned!");
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
LibGit2Sharp.Repository repo = null;
|
||||
await Task.Factory.StartNew(() =>
|
||||
|
||||
@@ -271,7 +271,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (portClosed)
|
||||
{
|
||||
reattachInformation.Port = nextPort;
|
||||
portAssignmentTcs.SetResult(true);
|
||||
portAssignmentTcs.TrySetResult(true);
|
||||
portAssignmentTcs = null;
|
||||
portClosed = false;
|
||||
}
|
||||
@@ -379,9 +379,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
lock (this)
|
||||
{
|
||||
if (portAssignmentTcs != null)
|
||||
{
|
||||
//someone was trying to change the port before us, ignore them
|
||||
//shouldn't happen anyway, add logging here
|
||||
portAssignmentTcs.SetResult(false);
|
||||
logger.LogWarning("Hey uhhh, this shouldn't happen ok? Pls to tell cyberboss. SessionController.SetPort");
|
||||
portAssignmentTcs.TrySetResult(false);
|
||||
}
|
||||
nextPort = port;
|
||||
portAssignmentTcs = new TaskCompletionSource<bool>();
|
||||
toWait = portAssignmentTcs.Task;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Octokit;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -9,6 +10,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
@@ -22,7 +24,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// <see cref="ModelController{TModel}"/> for <see cref="Administration"/>
|
||||
/// </summary>
|
||||
[Route(Api.Routes.Administration.Base)]
|
||||
[Route(Routes.Administration)]
|
||||
public sealed class AdministrationController : ModelController<Administration>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -79,7 +81,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
Logger.LogWarning("Exceeded GitHub rate limit!");
|
||||
var secondsString = Math.Ceiling((exception.Reset - DateTimeOffset.Now).TotalSeconds).ToString(CultureInfo.InvariantCulture);
|
||||
Response.Headers.Add("Retry-After", new Microsoft.Extensions.Primitives.StringValues { });
|
||||
Response.Headers.Add("Retry-After", new StringValues(secondsString));
|
||||
return StatusCode(RateLimitHttpStatusCode);
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
//if there's no instance user, do a weird thing and add all the instance roles
|
||||
//we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid
|
||||
//if user is null that means they got the token with an expired password
|
||||
var rightInt = authenticationContext.User == null || (RightsHelper.IsInstanceRight(I) && authenticationContext.InstanceUser == null) ? ~0 : authenticationContext.GetRight(I);
|
||||
var rightInt = authenticationContext.User == null || (RightsHelper.IsInstanceRight(I) && authenticationContext.InstanceUser == null) ? ~0U : authenticationContext.GetRight(I);
|
||||
var rightEnum = RightsHelper.RightToType(I);
|
||||
var right = (Enum)Enum.ToObject(rightEnum, rightInt);
|
||||
foreach (Enum J in Enum.GetValues(rightEnum))
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
@@ -17,7 +18,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Controller for managing <see cref="Api.Models.Byond.Version"/>s
|
||||
/// </summary>
|
||||
[Route("/" + nameof(Byond))]
|
||||
[Route(Routes.Byond)]
|
||||
public sealed class ByondController : ModelController<Api.Models.Byond>
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
@@ -19,10 +20,10 @@ using Z.EntityFramework.Plus;
|
||||
namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ModelController{TModel}"/> for managing <see cref="Api.Models.ChatSettings"/>
|
||||
/// <see cref="ModelController{TModel}"/> for managing <see cref="Api.Models.ChatBot"/>s
|
||||
/// </summary>
|
||||
[Route("/" + nameof(Components.Chat.Chat))]
|
||||
public sealed class ChatController : ModelController<Api.Models.ChatSettings>
|
||||
[Route(Routes.Chat)]
|
||||
public sealed class ChatController : ModelController<Api.Models.ChatBot>
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IInstanceManager"/> for the <see cref="ChatController"/>
|
||||
@@ -56,8 +57,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(ChatSettingsRights.Create)]
|
||||
public override async Task<IActionResult> Create([FromBody] Api.Models.ChatSettings model, CancellationToken cancellationToken)
|
||||
[TgsAuthorize(ChatBotRights.Create)]
|
||||
public override async Task<IActionResult> Create([FromBody] Api.Models.ChatBot model, CancellationToken cancellationToken)
|
||||
{
|
||||
if (model == null)
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
@@ -87,7 +88,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
return BadRequest(new ErrorMessage { Message = "One or more of channels aren't formatted correctly for the given provider!" });
|
||||
|
||||
//try to update das db first
|
||||
var dbModel = new Models.ChatSettings
|
||||
var dbModel = new Models.ChatBot
|
||||
{
|
||||
Name = model.Name,
|
||||
ConnectionString = model.ConnectionString,
|
||||
@@ -97,7 +98,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
Provider = model.Provider,
|
||||
};
|
||||
|
||||
DatabaseContext.ChatSettings.Add(dbModel);
|
||||
DatabaseContext.ChatBots.Add(dbModel);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -122,7 +123,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
catch
|
||||
{
|
||||
//undo the add
|
||||
DatabaseContext.ChatSettings.Remove(dbModel);
|
||||
DatabaseContext.ChatBots.Remove(dbModel);
|
||||
await DatabaseContext.Save(default).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
@@ -135,24 +136,24 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(ChatSettingsRights.Delete)]
|
||||
[TgsAuthorize(ChatBotRights.Delete)]
|
||||
public override async Task<IActionResult> Delete(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
var instance = instanceManager.GetInstance(Instance);
|
||||
await Task.WhenAll(instance.Chat.DeleteConnection(id, cancellationToken), DatabaseContext.ChatSettings.Where(x => x.Id == id).DeleteAsync(cancellationToken)).ConfigureAwait(false);
|
||||
await Task.WhenAll(instance.Chat.DeleteConnection(id, cancellationToken), DatabaseContext.ChatBots.Where(x => x.Id == id).DeleteAsync(cancellationToken)).ConfigureAwait(false);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(ChatSettingsRights.Read)]
|
||||
[TgsAuthorize(ChatBotRights.Read)]
|
||||
public override async Task<IActionResult> List(CancellationToken cancellationToken)
|
||||
{
|
||||
var query = DatabaseContext.ChatSettings.Where(x => x.InstanceId == Instance.Id).Include(x => x.Channels);
|
||||
var query = DatabaseContext.ChatBots.Where(x => x.InstanceId == Instance.Id).Include(x => x.Channels);
|
||||
|
||||
var results = await query.ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var connectionStrings = (AuthenticationContext.GetRight(RightsType.ChatSettings) & (int)ChatSettingsRights.ReadConnectionString) != 0;
|
||||
var connectionStrings = (AuthenticationContext.GetRight(RightsType.ChatBots) & (int)ChatBotRights.ReadConnectionString) != 0;
|
||||
|
||||
if (!connectionStrings)
|
||||
foreach (var I in results)
|
||||
@@ -162,16 +163,16 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(ChatSettingsRights.Read)]
|
||||
[TgsAuthorize(ChatBotRights.Read)]
|
||||
public override async Task<IActionResult> GetId(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = DatabaseContext.ChatSettings.Where(x => x.Id == id).Include(x => x.Channels);
|
||||
var query = DatabaseContext.ChatBots.Where(x => x.Id == id).Include(x => x.Channels);
|
||||
|
||||
var results = await query.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (results == default)
|
||||
return NotFound();
|
||||
|
||||
var connectionStrings = (AuthenticationContext.GetRight(RightsType.ChatSettings) & (int)ChatSettingsRights.ReadConnectionString) != 0;
|
||||
var connectionStrings = (AuthenticationContext.GetRight(RightsType.ChatBots) & (int)ChatBotRights.ReadConnectionString) != 0;
|
||||
|
||||
if (!connectionStrings)
|
||||
results.ConnectionString = null;
|
||||
@@ -180,24 +181,24 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(ChatSettingsRights.WriteChannels | ChatSettingsRights.WriteConnectionString | ChatSettingsRights.WriteEnabled | ChatSettingsRights.WriteName | ChatSettingsRights.WriteProvider)]
|
||||
public override async Task<IActionResult> Update([FromBody] Api.Models.ChatSettings model, CancellationToken cancellationToken)
|
||||
[TgsAuthorize(ChatBotRights.WriteChannels | ChatBotRights.WriteConnectionString | ChatBotRights.WriteEnabled | ChatBotRights.WriteName | ChatBotRights.WriteProvider)]
|
||||
public override async Task<IActionResult> Update([FromBody] Api.Models.ChatBot model, CancellationToken cancellationToken)
|
||||
{
|
||||
if (model == null)
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
|
||||
var query = DatabaseContext.ChatSettings.Where(x => x.InstanceId == Instance.Id && x.Id == model.Id).Include(x => x.Channels);
|
||||
var query = DatabaseContext.ChatBots.Where(x => x.InstanceId == Instance.Id && x.Id == model.Id).Include(x => x.Channels);
|
||||
|
||||
var current = await query.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (current == default)
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
|
||||
var userRights = (ChatSettingsRights)AuthenticationContext.GetRight(RightsType.ChatSettings);
|
||||
var userRights = (ChatBotRights)AuthenticationContext.GetRight(RightsType.ChatBots);
|
||||
|
||||
bool anySettingsModified = false;
|
||||
|
||||
bool CheckModified<T>(Expression<Func<Api.Models.Internal.ChatSettings, T>> expression, ChatSettingsRights requiredRight)
|
||||
bool CheckModified<T>(Expression<Func<Api.Models.Internal.ChatBot, T>> expression, ChatBotRights requiredRight)
|
||||
{
|
||||
var memberSelectorExpression = (MemberExpression)expression.Body;
|
||||
var property = (PropertyInfo)memberSelectorExpression.Member;
|
||||
@@ -213,11 +214,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
return false;
|
||||
};
|
||||
|
||||
if (CheckModified(x => x.ConnectionString, ChatSettingsRights.WriteConnectionString)
|
||||
|| CheckModified(x => x.Enabled, ChatSettingsRights.WriteEnabled)
|
||||
|| CheckModified(x => x.Name, ChatSettingsRights.WriteName)
|
||||
|| CheckModified(x => x.Provider, ChatSettingsRights.WriteProvider)
|
||||
|| (model.Channels != null && !userRights.HasFlag(ChatSettingsRights.WriteChannels)))
|
||||
if (CheckModified(x => x.ConnectionString, ChatBotRights.WriteConnectionString)
|
||||
|| CheckModified(x => x.Enabled, ChatBotRights.WriteEnabled)
|
||||
|| CheckModified(x => x.Name, ChatBotRights.WriteName)
|
||||
|| CheckModified(x => x.Provider, ChatBotRights.WriteProvider)
|
||||
|| (model.Channels != null && !userRights.HasFlag(ChatBotRights.WriteChannels)))
|
||||
return Forbid();
|
||||
|
||||
if (model.Channels != null)
|
||||
@@ -239,9 +240,9 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (model.Channels != null || anySettingsModified)
|
||||
await chat.ChangeChannels(current.Id, current.Channels, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (userRights.HasFlag(ChatSettingsRights.Read))
|
||||
if (userRights.HasFlag(ChatBotRights.Read))
|
||||
{
|
||||
if (!userRights.HasFlag(ChatSettingsRights.ReadConnectionString))
|
||||
if (!userRights.HasFlag(ChatBotRights.ReadConnectionString))
|
||||
current.ConnectionString = null;
|
||||
return Json(current.ToApi());
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
@@ -15,7 +16,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// The <see cref="ModelController{TModel}"/> for <see cref="ConfigurationFile"/>s
|
||||
/// </summary>
|
||||
[Route("/" + nameof(Configuration))]
|
||||
[Route(Routes.Configuration)]
|
||||
public sealed class ConfigurationController : ModelController<ConfigurationFile>
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
@@ -21,7 +22,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// <see cref="ModelController{TModel}"/> for managing the <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
[Route("/" + nameof(DreamDaemon))]
|
||||
[Route(Routes.DreamDaemon)]
|
||||
public sealed class DreamDaemonController : ModelController<DreamDaemon>
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -6,6 +6,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Core;
|
||||
@@ -17,7 +18,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Controller for managing the compiler
|
||||
/// </summary>
|
||||
[Route("/" + nameof(Api.Models.DreamMaker))]
|
||||
[Route(Routes.DreamMaker)]
|
||||
public sealed class DreamMakerController : ModelController<Api.Models.DreamMaker>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -58,6 +59,29 @@ namespace Tgstation.Server.Host.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(DreamMakerRights.List)]
|
||||
public override async Task<IActionResult> GetId(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
var compileJob = await DatabaseContext.CompileJobs
|
||||
.Where(x => x.Id == id && x.Job.Instance.Id == Instance.Id)
|
||||
.Include(x => x.Job).ThenInclude(x => x.StartedBy)
|
||||
.Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge)
|
||||
.Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge)
|
||||
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (compileJob == default)
|
||||
return NotFound();
|
||||
return Json(compileJob.ToApi());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(DreamMakerRights.List)]
|
||||
public override async Task<IActionResult> List(CancellationToken cancellationToken)
|
||||
{
|
||||
var compileJobs = await DatabaseContext.CompileJobs.Where(x => x.Job.Instance.Id == Instance.Id).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
return Json(compileJobs.Select(x => x.ToApi()));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(DreamMakerRights.Compile)]
|
||||
public override async Task<IActionResult> Create([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken)
|
||||
@@ -75,15 +99,33 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(DreamMakerRights.SetDme)]
|
||||
[TgsAuthorize(DreamMakerRights.SetDme | DreamMakerRights.SetApiValidationPort)]
|
||||
public override async Task<IActionResult> Update([FromBody] Api.Models.DreamMaker model, CancellationToken cancellationToken)
|
||||
{
|
||||
var hostModel = new DreamMakerSettings
|
||||
{
|
||||
InstanceId = Instance.Id
|
||||
};
|
||||
|
||||
DatabaseContext.DreamMakerSettings.Attach(hostModel);
|
||||
hostModel.ProjectName = model.ProjectName;
|
||||
|
||||
if (model.ProjectName != null)
|
||||
{
|
||||
if (!AuthenticationContext.InstanceUser.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetDme))
|
||||
return Forbid();
|
||||
if (model.ProjectName.Length == 0)
|
||||
hostModel.ProjectName = null;
|
||||
else
|
||||
hostModel.ProjectName = model.ProjectName;
|
||||
}
|
||||
|
||||
if (model.ApiValidationPort.HasValue)
|
||||
{
|
||||
if (!AuthenticationContext.InstanceUser.DreamMakerRights.Value.HasFlag(DreamMakerRights.SetApiValidationPort))
|
||||
return Forbid();
|
||||
hostModel.ApiValidationPort = model.ApiValidationPort;
|
||||
}
|
||||
|
||||
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
return await Read(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -104,8 +146,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
var ddSettingsTask = databaseContext.DreamDaemonSettings.Where(x => x.InstanceId == instanceModel.Id).Select(x => new DreamDaemonSettings{
|
||||
StartupTimeout = x.StartupTimeout,
|
||||
SecurityLevel = x.SecurityLevel
|
||||
}).FirstOrDefaultAsync(cancellationToken);
|
||||
var projectName = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == instanceModel.Id).Select(x => x.ProjectName).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
}).FirstAsync(cancellationToken);
|
||||
var dreamMakerSettings = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == instanceModel.Id).FirstAsync(cancellationToken).ConfigureAwait(false);
|
||||
var ddSettings = await ddSettingsTask.ConfigureAwait(false);
|
||||
|
||||
var instance = instanceManager.GetInstance(instanceModel);
|
||||
@@ -136,7 +178,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
databaseContext.Instances.Attach(revInfo.Instance);
|
||||
}
|
||||
|
||||
compileJob = await instance.DreamMaker.Compile(revInfo, projectName, ddSettings.SecurityLevel.Value, ddSettings.StartupTimeout.Value, repo, cancellationToken).ConfigureAwait(false);
|
||||
compileJob = await instance.DreamMaker.Compile(revInfo, dreamMakerSettings, ddSettings.SecurityLevel.Value, ddSettings.StartupTimeout.Value, repo, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (compileJob.DMApiValidated != true)
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Main <see cref="ApiController"/> for the <see cref="Application"/>
|
||||
/// </summary>
|
||||
[Route("/")]
|
||||
[Route(Routes.Root)]
|
||||
public sealed class HomeController : ApiController
|
||||
{
|
||||
/// <summary>
|
||||
@@ -124,9 +124,9 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (!user.Enabled.Value)
|
||||
return Forbid();
|
||||
|
||||
var token = tokenFactory.CreateToken(user, out var expiry);
|
||||
var token = tokenFactory.CreateToken(user);
|
||||
if (identity != null)
|
||||
identityCache.CacheSystemIdentity(user, identity, expiry.AddMinutes(1)); //expire the identity slightly after the auth token in case of lag
|
||||
identityCache.CacheSystemIdentity(user, identity, token.ExpiresAt.Value.AddMinutes(1)); //expire the identity slightly after the auth token in case of lag
|
||||
|
||||
Logger.LogDebug("Successfully logged in user {0}!", user.Id);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
@@ -25,7 +26,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Controller for managing <see cref="Components.Instance"/>s
|
||||
/// </summary>
|
||||
[Route("/Instance")]
|
||||
[Route(Routes.InstanceManager)]
|
||||
public sealed class InstanceController : ModelController<Api.Models.Instance>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -80,13 +81,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
Models.InstanceUser InstanceAdminUser() => new Models.InstanceUser
|
||||
{
|
||||
ByondRights = (ByondRights)~0,
|
||||
ChatSettingsRights = (ChatSettingsRights)~0,
|
||||
ConfigurationRights = (ConfigurationRights)~0,
|
||||
DreamDaemonRights = (DreamDaemonRights)~0,
|
||||
DreamMakerRights = (DreamMakerRights)~0,
|
||||
RepositoryRights = (RepositoryRights)~0,
|
||||
InstanceUserRights = (InstanceUserRights)~0,
|
||||
ByondRights = (ByondRights)~0U,
|
||||
ChatBotRights = (ChatBotRights)~0U,
|
||||
ConfigurationRights = (ConfigurationRights)~0U,
|
||||
DreamDaemonRights = (DreamDaemonRights)~0U,
|
||||
DreamMakerRights = (DreamMakerRights)~0U,
|
||||
RepositoryRights = (RepositoryRights)~0U,
|
||||
InstanceUserRights = (InstanceUserRights)~0U,
|
||||
UserId = AuthenticationContext.User.Id
|
||||
};
|
||||
|
||||
@@ -122,7 +123,10 @@ namespace Tgstation.Server.Host.Controllers
|
||||
SoftShutdown = false,
|
||||
StartupTimeout = 20
|
||||
},
|
||||
DreamMakerSettings = new DreamMakerSettings(),
|
||||
DreamMakerSettings = new DreamMakerSettings
|
||||
{
|
||||
ApiValidationPort = 1339
|
||||
},
|
||||
Name = model.Name,
|
||||
Online = false,
|
||||
Path = model.Path,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
@@ -7,6 +6,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Models;
|
||||
@@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// For managing <see cref="User"/>s
|
||||
/// </summary>
|
||||
[Route("/" + nameof(Models.InstanceUser))]
|
||||
[Route(Routes.InstanceUser)]
|
||||
public sealed class InstanceUserController : ModelController<Api.Models.InstanceUser>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -57,7 +57,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
var dbUser = new Models.InstanceUser
|
||||
{
|
||||
ByondRights = model.ByondRights ?? ByondRights.None,
|
||||
ChatSettingsRights = model.ChatSettingsRights ?? ChatSettingsRights.None,
|
||||
ChatBotRights = model.ChatBotRights ?? ChatBotRights.None,
|
||||
ConfigurationRights = model.ConfigurationRights ?? ConfigurationRights.None,
|
||||
DreamDaemonRights = model.DreamDaemonRights ?? DreamDaemonRights.None,
|
||||
DreamMakerRights = model.DreamMakerRights ?? DreamMakerRights.None,
|
||||
@@ -93,7 +93,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
|
||||
originalUser.ByondRights = model.ByondRights ?? originalUser.ByondRights;
|
||||
originalUser.ChatSettingsRights = model.ChatSettingsRights ?? originalUser.ChatSettingsRights;
|
||||
originalUser.ChatBotRights = model.ChatBotRights ?? originalUser.ChatBotRights;
|
||||
originalUser.ConfigurationRights = model.ConfigurationRights ?? originalUser.ConfigurationRights;
|
||||
originalUser.DreamDaemonRights = model.DreamDaemonRights ?? originalUser.DreamDaemonRights;
|
||||
originalUser.DreamMakerRights = model.DreamMakerRights ?? originalUser.DreamMakerRights;
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
@@ -15,7 +16,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// <see cref="ModelController{TModel}"/> for <see cref="Api.Models.Job"/>s
|
||||
/// </summary>
|
||||
[Route("/" + nameof(Job))]
|
||||
[Route(Routes.Jobs)]
|
||||
public sealed class JobController : ModelController<Api.Models.Job>
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
@@ -23,7 +24,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Controller for managing the <see cref="Repository"/>s
|
||||
/// </summary>
|
||||
[Route("/" + nameof(Repository))]
|
||||
[Route(Routes.Repository)]
|
||||
public sealed class RepositoryController : ModelController<Repository>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -63,7 +64,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
IQueryable<Models.RevisionInformation> queryTarget = databaseContext.RevisionInformations;
|
||||
|
||||
var revisionInfo = await databaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
|
||||
var revisionInfo = await databaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
|
||||
.Include(x => x.CompileJobs)
|
||||
.Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge) //minimal info, they can query the rest if they're allowed
|
||||
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); //search every rev info because LOL SHA COLLISIONS
|
||||
@@ -138,6 +139,9 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
var repoManager = instanceManager.GetInstance(Instance).RepositoryManager;
|
||||
|
||||
if (repoManager.CloneInProgress)
|
||||
return Conflict();
|
||||
|
||||
using (var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
if (repo != null)
|
||||
@@ -243,9 +247,12 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (model.Origin != null)
|
||||
return BadRequest(new ErrorMessage { Message = "origin cannot be modified without deleting the repository!" });
|
||||
|
||||
if(model.NewTestMerges.Any(x => !x.Number.HasValue))
|
||||
if(model.NewTestMerges?.Any(x => !x.Number.HasValue) == true)
|
||||
return BadRequest(new ErrorMessage { Message = "All new test merges must provide a number!" });
|
||||
|
||||
if(model.NewTestMerges?.Any(x => model.NewTestMerges.Any(y => x != y && x.Number == y.Number)) == true)
|
||||
return BadRequest(new ErrorMessage { Message = "Cannot test merge the same PR twice in one job!" });
|
||||
|
||||
var newTestMerges = model.NewTestMerges != null && model.NewTestMerges.Count > 0;
|
||||
var userRights = (RepositoryRights)AuthenticationContext.GetRight(RightsType.Repository);
|
||||
if (newTestMerges && !userRights.HasFlag(RepositoryRights.MergePullRequest))
|
||||
@@ -282,7 +289,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|| (model.UpdateFromOrigin == true && !userRights.HasFlag(RepositoryRights.UpdateBranch)))
|
||||
return Forbid();
|
||||
|
||||
if (currentModel.AccessToken.Length == 0 && currentModel.AccessUser.Length == 0)
|
||||
if (currentModel.AccessToken?.Length == 0 && currentModel.AccessUser?.Length == 0)
|
||||
{
|
||||
//setting an empty string clears everything
|
||||
currentModel.AccessUser = null;
|
||||
@@ -359,7 +366,10 @@ namespace Tgstation.Server.Host.Controllers
|
||||
throw new InvalidOperationException("Merge conflict occurred during origin update!");
|
||||
await UpdateRevInfo().ConfigureAwait(false);
|
||||
if (fastForward.Value)
|
||||
{
|
||||
lastRevisionInfo.OriginCommitSha = repo.Head;
|
||||
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, true, ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,50 +395,94 @@ namespace Tgstation.Server.Host.Controllers
|
||||
lastRevisionInfo.OriginCommitSha = repo.Head;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Dictionary<int, Octokit.PullRequest> prMap = null;
|
||||
//test merging
|
||||
if (newTestMerges)
|
||||
{
|
||||
//optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out
|
||||
Models.RevisionInformation revInfoWereLookingFor = null;
|
||||
if(lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha)
|
||||
{
|
||||
foreach (var I in model.NewTestMerges)
|
||||
//normalize the shas to lowercase ala libgit2
|
||||
#pragma warning disable CA1308 // Normalize strings to uppercase
|
||||
I.PullRequestRevision = I.PullRequestRevision?.ToLowerInvariant();
|
||||
#pragma warning restore CA1308 // Normalize strings to uppercase
|
||||
//bit of sanitization
|
||||
foreach (var I in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.PullRequestRevision)))
|
||||
I.PullRequestRevision = null;
|
||||
|
||||
revInfoWereLookingFor = await databaseContext.RevisionInformations
|
||||
.Where(x => x.OriginCommitSha == lastRevisionInfo.OriginCommitSha && x.ActiveTestMerges.Count == model.NewTestMerges.Count)
|
||||
//split here cause this bit probably has to be done locally
|
||||
.Where(x => x.ActiveTestMerges.Select(y => y.TestMerge)
|
||||
.All(y => model.NewTestMerges.Any(z => y.Number == z.Number && y.PullRequestRevision.StartsWith(z.PullRequestRevision, StringComparison.Ordinal))))
|
||||
.Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge)
|
||||
.FirstOrDefaultAsync(ct).ConfigureAwait(false);
|
||||
var gitHubClient = currentModel.AccessToken != null ? gitHubClientFactory.CreateClient(currentModel.AccessToken) : gitHubClientFactory.CreateClient();
|
||||
|
||||
var repoOwner = repo.GitHubOwner;
|
||||
var repoName = repo.GitHubRepoName;
|
||||
|
||||
Models.RevisionInformation revInfoWereLookingFor = null;
|
||||
//optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out
|
||||
if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha)
|
||||
{
|
||||
//In order for this to work though we need the shas of all the commits
|
||||
if (model.NewTestMerges.Any(x => x.PullRequestRevision == null))
|
||||
prMap = new Dictionary<int, Octokit.PullRequest>();
|
||||
|
||||
bool cantSearch = false;
|
||||
foreach (var I in model.NewTestMerges)
|
||||
{
|
||||
if (I.PullRequestRevision != null)
|
||||
//normalize the shas to lowercase ala libgit2
|
||||
#pragma warning disable CA1308 // Normalize strings to uppercase
|
||||
I.PullRequestRevision = I.PullRequestRevision?.ToLowerInvariant();
|
||||
#pragma warning restore CA1308 // Normalize strings to uppercase
|
||||
else
|
||||
//retrieve the latest sha
|
||||
try
|
||||
{
|
||||
var pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number.Value).ConfigureAwait(false);
|
||||
prMap.Add(I.Number.Value, pr);
|
||||
I.PullRequestRevision = pr.Head.Sha;
|
||||
}
|
||||
catch
|
||||
{
|
||||
cantSearch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cantSearch)
|
||||
{
|
||||
var dbPull = await databaseContext.RevisionInformations
|
||||
.Where(x => x.Instance.Id == Instance.Id
|
||||
&& x.OriginCommitSha == lastRevisionInfo.OriginCommitSha
|
||||
&& x.ActiveTestMerges.Count == model.NewTestMerges.Count)
|
||||
.Include(x => x.ActiveTestMerges)
|
||||
.ThenInclude(x => x.TestMerge)
|
||||
.ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
//split here cause this bit has to be done locally
|
||||
revInfoWereLookingFor = dbPull
|
||||
.Where(x => x.ActiveTestMerges.Select(y => y.TestMerge)
|
||||
.All(y => model.NewTestMerges.Any(z =>
|
||||
y.Number == z.Number
|
||||
&& y.PullRequestRevision.StartsWith(z.PullRequestRevision, StringComparison.Ordinal)
|
||||
&& y.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null)))
|
||||
.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (revInfoWereLookingFor != null)
|
||||
{
|
||||
//goteem
|
||||
await repo.ResetToSha(revInfoWereLookingFor.CommitSha, cancellationToken).ConfigureAwait(false);
|
||||
lastRevisionInfo = revInfoWereLookingFor;
|
||||
}
|
||||
else
|
||||
{
|
||||
var gitHubClient = currentModel.AccessToken != null ? gitHubClientFactory.CreateClient(currentModel.AccessToken) : gitHubClientFactory.CreateClient();
|
||||
var contextUser = new Models.User
|
||||
{
|
||||
Id = AuthenticationContext.User.Id
|
||||
};
|
||||
databaseContext.Users.Attach(contextUser);
|
||||
|
||||
var repoOwner = repo.GitHubOwner;
|
||||
var repoName = repo.GitHubRepoName;
|
||||
foreach (var I in model.NewTestMerges)
|
||||
{
|
||||
Octokit.PullRequest pr = null;
|
||||
string errorMessage = null;
|
||||
try
|
||||
{
|
||||
pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number.Value).ConfigureAwait(false);
|
||||
//load from cache if possible
|
||||
if (prMap == null || !prMap.TryGetValue(I.Number.Value, out pr))
|
||||
pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number.Value).ConfigureAwait(false);
|
||||
}
|
||||
catch (Octokit.RateLimitExceededException)
|
||||
{
|
||||
@@ -477,9 +531,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//never synchronize with test merges
|
||||
if (startSha != repo.Head && lastRevisionInfo.ActiveTestMerges.Count == 0)
|
||||
|
||||
if (startSha != repo.Head)
|
||||
{
|
||||
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, false, ct).ConfigureAwait(false);
|
||||
await UpdateRevInfo().ConfigureAwait(false);
|
||||
|
||||
@@ -52,10 +52,10 @@ namespace Tgstation.Server.Host.Controllers
|
||||
public TgsAuthorizeAttribute(DreamDaemonRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights);
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="ChatSettingsRights"/>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="ChatBotRights"/>
|
||||
/// </summary>
|
||||
/// <param name="requiredRights">The rights required</param>
|
||||
public TgsAuthorizeAttribute(ChatSettingsRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights);
|
||||
public TgsAuthorizeAttribute(ChatBotRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights);
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="ConfigurationRights"/>
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
@@ -20,7 +21,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// For managing <see cref="User"/>s
|
||||
/// </summary>
|
||||
[Route("/" + nameof(Models.User))]
|
||||
[Route(Routes.User)]
|
||||
public sealed class UserController : ModelController<UserUpdate>
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
+3
-4
@@ -1,12 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class ChatSettings : Api.Models.Internal.ChatSettings, IApiConvertable<Api.Models.ChatSettings>
|
||||
public sealed class ChatBot : Api.Models.Internal.ChatBot, IApiConvertable<Api.Models.ChatBot>
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance.Id"/>
|
||||
@@ -20,12 +19,12 @@ namespace Tgstation.Server.Host.Models
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="Api.Models.ChatSettings.Channels"/>
|
||||
/// See <see cref="Api.Models.ChatBot.Channels"/>
|
||||
/// </summary>
|
||||
public List<ChatChannel> Channels { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Api.Models.ChatSettings ToApi() => new Api.Models.ChatSettings
|
||||
public Api.Models.ChatBot ToApi() => new Api.Models.ChatBot
|
||||
{
|
||||
Channels = Channels.Select(x => x.ToApi()).ToList(),
|
||||
ConnectionString = ConnectionString,
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user