mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-23 04:57:17 +01:00
Definition looks good now
This commit is contained in:
@@ -8,6 +8,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The ID of the entity.
|
||||
/// </summary>
|
||||
[RequestOptions(FieldPresence.Required)]
|
||||
[RequestOptions(FieldPresence.Ignored, PutOnly = true)]
|
||||
public virtual long? Id { get; set; }
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace Tgstation.Server.Api.Models
|
||||
ConfigurationDirectoryNotEmpty,
|
||||
|
||||
/// <summary>
|
||||
/// Tried to clone a repository with a missing <see cref="RepositoryCloneRequest.Origin"/> property.
|
||||
/// Tried to clone a repository with a missing <see cref="RepositoryCreateRequest.Origin"/> property.
|
||||
/// </summary>
|
||||
[Obsolete("Deprecated")]
|
||||
[Description("Cannot clone repository with missing origin field!")]
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// <summary>
|
||||
/// Represents a deployment run.
|
||||
/// </summary>
|
||||
public class CompileJob : EntityId
|
||||
public abstract class CompileJob : EntityId
|
||||
{
|
||||
/// <summary>
|
||||
/// The .dme file used for compilation
|
||||
@@ -19,7 +19,6 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// Textual output of DM
|
||||
/// </summary>
|
||||
[Required]
|
||||
[ResponseOptions]
|
||||
public string? Output { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -29,8 +28,9 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
public Guid? DirectoryName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The minimum <see cref="DreamDaemonSecurity"/> required to run the <see cref="CompileJob"/>'s output
|
||||
/// The minimum <see cref="DreamDaemonSecurity"/> required to run the <see cref="CompileJob"/>'s output.
|
||||
/// </summary>
|
||||
[ResponseOptions]
|
||||
public DreamDaemonSecurity? MinimumSecurityLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// <summary>
|
||||
/// The <see cref="EntityId.Id"/> of the <see cref="PermissionSet"/> the <see cref="InstancePermissionSet"/> belongs to
|
||||
/// </summary>
|
||||
[RequestOptions(FieldPresence.Required)]
|
||||
public long PermissionSetId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// The branch or tag HEAD points to.
|
||||
/// </summary>
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
[ResponseOptions]
|
||||
public string? Reference { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// <summary>
|
||||
/// The <see cref="Models.PermissionSet"/> directly associated with the <see cref="UserResponse"/>.
|
||||
/// </summary>
|
||||
[ResponseOptions]
|
||||
public PermissionSet? PermissionSet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -6,13 +6,12 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// <summary>
|
||||
/// Represents a server user.
|
||||
/// </summary>
|
||||
public abstract class UserModelBase : NamedEntity
|
||||
public abstract class UserModelBase : UserName
|
||||
{
|
||||
/// <summary>
|
||||
/// If the <see cref="UserModelBase"/> is enabled since users cannot be deleted. System users cannot be disabled
|
||||
/// </summary>
|
||||
[Required]
|
||||
[ResponseOptions]
|
||||
public bool? Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -20,7 +19,6 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// </summary>
|
||||
[Required]
|
||||
[RequestOptions(FieldPresence.Ignored)]
|
||||
[ResponseOptions]
|
||||
public DateTimeOffset? CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,15 +5,14 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// Base class for named entities.
|
||||
/// </summary>
|
||||
public class NamedEntity : EntityId
|
||||
public abstract class NamedEntity : EntityId
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the entity represented by the <see cref="NamedEntity"/>.
|
||||
/// </summary>
|
||||
[Required]
|
||||
[ResponseOptions]
|
||||
[RequestOptions(FieldPresence.Required, PutOnly = true)]
|
||||
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
|
||||
public string? Name { get; set; }
|
||||
public virtual string? Name { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a request to update a chat bot.
|
||||
/// </summary>
|
||||
public sealed class ChatBotCreateRequest : ChatBotUpdateRequest
|
||||
public sealed class ChatBotCreateRequest : ChatBotApiBase
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// A request to create an <see cref="Instance"/>.
|
||||
/// </summary>
|
||||
public sealed class InstanceCreateRequest : InstanceUpdateRequest
|
||||
public sealed class InstanceCreateRequest : Instance
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// Represents a request to clone the repository.
|
||||
/// </summary>
|
||||
public sealed class RepositoryCloneRequest : RepositoryApiBase
|
||||
public sealed class RepositoryCreateRequest : RepositoryApiBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The origin URL to clone.
|
||||
@@ -5,7 +5,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// Indicates the <see cref="FieldPresence"/> for fields in models.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
|
||||
public sealed class RequestOptionsAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Tgstation.Server.Api.Models
|
||||
public string? Path { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[ResponseOptions]
|
||||
public string? LastReadHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// The current <see cref="WatchdogStatus"/>.
|
||||
/// </summary>
|
||||
[EnumDataType(typeof(WatchdogStatus))]
|
||||
[ResponseOptions]
|
||||
public WatchdogStatus? Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -8,13 +8,13 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The <see cref="UserResponse"/> that started the job
|
||||
/// </summary>
|
||||
public UserResponse? StartedBy { get; set; }
|
||||
public UserName? StartedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="UserResponse"/> that cancelled the job
|
||||
/// </summary>
|
||||
[ResponseOptions]
|
||||
public UserResponse? CancelledBy { get; set; }
|
||||
public UserName? CancelledBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional progress between 0 and 100 inclusive
|
||||
|
||||
@@ -9,6 +9,6 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The <see cref="NamedEntity"/>s the <see cref="UserGroupResponse"/> has.
|
||||
/// </summary>
|
||||
public ICollection<NamedEntity>? Users { get; set; }
|
||||
public ICollection<UserName>? Users { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,6 @@ namespace Tgstation.Server.Api.Models
|
||||
/// The <see cref="UserResponse"/> who created this <see cref="UserResponse"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public NamedEntity? CreatedBy { get; set; }
|
||||
public UserName? CreatedBy { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using System;
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates API fields that may be null on response.
|
||||
/// Indicates the response <see cref="FieldPresence"/> of API fields. Changes it from <see cref="FieldPresence.Required"/> to <see cref="FieldPresence.Optional"/> by default.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class ResponseOptionsAttribute : Attribute
|
||||
|
||||
@@ -8,6 +8,6 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The <see cref="NamedEntity"/> of the user who created the <see cref="TestMerge"/>.
|
||||
/// </summary>
|
||||
public NamedEntity? MergedBy { get; set; }
|
||||
public UserName? MergedBy { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for user names.
|
||||
/// </summary>
|
||||
public class UserName : NamedEntity
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[RequestOptions(FieldPresence.Optional)]
|
||||
public override string? Name
|
||||
{
|
||||
get => base.Name;
|
||||
set => base.Name = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a copy of the <see cref="UserName"/> as a given <typeparamref name="TResultType"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TResultType">The child of <see cref="UserName"/> to create.</typeparam>
|
||||
/// <returns>A new <typeparamref name="TResultType"/> copied from <see langword="this"/>.</returns>
|
||||
protected virtual TResultType CreateUserName<TResultType>() where TResultType : UserName, new() => new TResultType
|
||||
{
|
||||
Id = Id,
|
||||
Name = Name
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create a copy of the <see cref="UserName"/>.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="UserName"/> copied from <see langword="this"/>.</returns>
|
||||
public UserName CreateUserName() => CreateUserName<UserName>();
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,10 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <summary>
|
||||
/// Clones a <paramref name="repository"/>
|
||||
/// </summary>
|
||||
/// <param name="repository">The <see cref="RepositoryCloneRequest"/>.</param>
|
||||
/// <param name="repository">The <see cref="RepositoryCreateRequest"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="RepositoryResponse"/>/</returns>
|
||||
Task<RepositoryResponse> Clone(RepositoryCloneRequest repository, CancellationToken cancellationToken);
|
||||
Task<RepositoryResponse> Clone(RepositoryCreateRequest repository, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the repository.
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Tgstation.Server.Client.Components
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<RepositoryResponse> Clone(RepositoryCloneRequest repository, CancellationToken cancellationToken) => apiClient.Create<RepositoryCloneRequest, RepositoryResponse>(Routes.Repository, repository, instance.Id!.Value, cancellationToken);
|
||||
public Task<RepositoryResponse> Clone(RepositoryCreateRequest repository, CancellationToken cancellationToken) => apiClient.Create<RepositoryCreateRequest, RepositoryResponse>(Routes.Repository, repository, instance.Id!.Value, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<RepositoryResponse> Delete(CancellationToken cancellationToken) => apiClient.Delete<RepositoryResponse>(Routes.Repository, instance.Id!.Value, cancellationToken);
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
IJobManager jobManager,
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
ILogger<DiscordProvider> logger,
|
||||
Models.ChatBot chatBot)
|
||||
ChatBot chatBot)
|
||||
: base(jobManager, logger, chatBot)
|
||||
{
|
||||
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[Route("/Bridge")]
|
||||
[Produces(MediaTypeNames.Application.Json)]
|
||||
[ApiController]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
public class BridgeController : Controller
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -11,6 +11,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Database;
|
||||
@@ -65,14 +66,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Create a new chat bot <paramref name="model"/>.
|
||||
/// </summary>
|
||||
/// <param name="model">The <see cref="ChatBotUpdateRequest"/>.</param>
|
||||
/// <param name="model">The <see cref="ChatBotCreateRequest"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> for the operation.</returns>
|
||||
/// <response code="201">Created <see cref="ChatBot"/> successfully.</response>
|
||||
[HttpPut]
|
||||
[TgsAuthorize(ChatBotRights.Create)]
|
||||
[ProducesResponseType(typeof(ChatBotResponse), 201)]
|
||||
public async Task<IActionResult> Create([FromBody] ChatBotUpdateRequest model, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> Create([FromBody] ChatBotCreateRequest model, CancellationToken cancellationToken)
|
||||
{
|
||||
if (model == null)
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
@@ -278,7 +279,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
bool anySettingsModified = false;
|
||||
|
||||
bool CheckModified<T>(Expression<Func<Api.Models.Internal.ChatBotSettings, T>> expression, ChatBotRights requiredRight)
|
||||
bool CheckModified<T>(Expression<Func<ChatBotSettings, T>> expression, ChatBotRights requiredRight)
|
||||
{
|
||||
var memberSelectorExpression = (MemberExpression)expression.Body;
|
||||
var property = (PropertyInfo)memberSelectorExpression.Member;
|
||||
@@ -350,10 +351,10 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Perform some basic validation of a given <paramref name="model"/>.
|
||||
/// </summary>
|
||||
/// <param name="model">The <see cref="ChatBotUpdateRequest"/> to validate.</param>
|
||||
/// <param name="model">The <see cref="ChatBotApiBase"/> to validate.</param>
|
||||
/// <param name="forCreation">If the <paramref name="model"/> is being created.</param>
|
||||
/// <returns>An <see cref="IActionResult"/> to respond with or <see langword="null"/>.</returns>
|
||||
private IActionResult StandardModelChecks(ChatBotUpdateRequest model, bool forCreation)
|
||||
private IActionResult StandardModelChecks(ChatBotApiBase model, bool forCreation)
|
||||
{
|
||||
if (model.ReconnectionInterval == 0)
|
||||
throw new InvalidOperationException("RecconnectionInterval cannot be zero!");
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// Controller for the web control panel.
|
||||
/// </summary>
|
||||
[Route(Application.ControlPanelRoute)]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
public class ControlPanelController : Controller
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -67,17 +67,17 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation.</returns>
|
||||
/// <response code="202"><see cref="Api.Models.JobResponse"/> to launch the watchdog started successfully.</response>
|
||||
/// <response code="202"><see cref="JobResponse"/> to launch the watchdog started successfully.</response>
|
||||
[HttpPut]
|
||||
[TgsAuthorize(DreamDaemonRights.Start)]
|
||||
[ProducesResponseType(typeof(Api.Models.JobResponse), 202)]
|
||||
[ProducesResponseType(typeof(JobResponse), 202)]
|
||||
public Task<IActionResult> Create(CancellationToken cancellationToken)
|
||||
=> WithComponentInstance(async instance =>
|
||||
{
|
||||
if (instance.Watchdog.Status != WatchdogStatus.Offline)
|
||||
return Conflict(new ErrorMessageResponse(ErrorCode.WatchdogRunning));
|
||||
|
||||
var job = new Models.Job
|
||||
var job = new Job
|
||||
{
|
||||
Description = "Launch DreamDaemon",
|
||||
CancelRight = (ulong)DreamDaemonRights.Shutdown,
|
||||
@@ -298,18 +298,18 @@ namespace Tgstation.Server.Host.Controllers
|
||||
#pragma warning restore CA1502
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Api.Models.JobResponse"/> to restart the Watchdog. It will not start if it wasn't already running.
|
||||
/// Creates a <see cref="JobResponse"/> to restart the Watchdog. It will not start if it wasn't already running.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the request</returns>
|
||||
/// <response code="202">Restart <see cref="Api.Models.JobResponse"/> started successfully.</response>
|
||||
/// <response code="202">Restart <see cref="JobResponse"/> started successfully.</response>
|
||||
[HttpPatch]
|
||||
[TgsAuthorize(DreamDaemonRights.Restart)]
|
||||
[ProducesResponseType(typeof(Api.Models.JobResponse), 202)]
|
||||
[ProducesResponseType(typeof(JobResponse), 202)]
|
||||
public Task<IActionResult> Restart(CancellationToken cancellationToken)
|
||||
=> WithComponentInstance(async instance =>
|
||||
{
|
||||
var job = new Models.Job
|
||||
var job = new Job
|
||||
{
|
||||
Instance = Instance,
|
||||
CancelRightsType = RightsType.DreamDaemon,
|
||||
@@ -332,18 +332,18 @@ namespace Tgstation.Server.Host.Controllers
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Api.Models.JobResponse"/> to generate a DreamDaemon process dump.
|
||||
/// Creates a <see cref="JobResponse"/> to generate a DreamDaemon process dump.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the request</returns>
|
||||
/// <response code="202">Dump <see cref="Api.Models.JobResponse"/> started successfully.</response>
|
||||
/// <response code="202">Dump <see cref="JobResponse"/> started successfully.</response>
|
||||
[HttpPatch(Routes.Diagnostics)]
|
||||
[TgsAuthorize(DreamDaemonRights.CreateDump)]
|
||||
[ProducesResponseType(typeof(Api.Models.JobResponse), 202)]
|
||||
[ProducesResponseType(typeof(JobResponse), 202)]
|
||||
public Task<IActionResult> CreateDump(CancellationToken cancellationToken)
|
||||
=> WithComponentInstance(async instance =>
|
||||
{
|
||||
var job = new Models.Job
|
||||
var job = new Job
|
||||
{
|
||||
Instance = Instance,
|
||||
CancelRightsType = RightsType.DreamDaemon,
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions));
|
||||
}
|
||||
|
||||
async Task<Models.Instance> CreateDefaultInstance(InstanceUpdateRequest initialSettings, CancellationToken cancellationToken)
|
||||
async Task<Models.Instance> CreateDefaultInstance(InstanceCreateRequest initialSettings, CancellationToken cancellationToken)
|
||||
{
|
||||
var ddPort = await portAllocator.GetAvailablePort(1, false, cancellationToken).ConfigureAwait(false);
|
||||
if (!ddPort.HasValue)
|
||||
@@ -222,7 +222,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Create or attach an <see cref="Api.Models.Instance"/>.
|
||||
/// </summary>
|
||||
/// <param name="model">The <see cref="Api.Models.Instance"/> settings.</param>
|
||||
/// <param name="model">The <see cref="InstanceCreateRequest"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the request.</returns>
|
||||
/// <response code="200">Instance attached successfully.</response>
|
||||
@@ -231,7 +231,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[TgsAuthorize(InstanceManagerRights.Create)]
|
||||
[ProducesResponseType(typeof(InstanceResponse), 200)]
|
||||
[ProducesResponseType(typeof(InstanceResponse), 201)]
|
||||
public async Task<IActionResult> Create([FromBody] InstanceUpdateRequest model, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> Create([FromBody] InstanceCreateRequest model, CancellationToken cancellationToken)
|
||||
{
|
||||
if (model == null)
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Begin cloning the repository if it doesn't exist.
|
||||
/// </summary>
|
||||
/// <param name="model">The <see cref="RepositoryCloneRequest"/>.</param>
|
||||
/// <param name="model">The <see cref="RepositoryCreateRequest"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the request.</returns>
|
||||
/// <response code="201">The repository was created successfully and the <see cref="Api.Models.JobResponse"/> to clone it has begun.</response>
|
||||
@@ -139,7 +139,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[TgsAuthorize(RepositoryRights.SetOrigin)]
|
||||
[ProducesResponseType(typeof(RepositoryResponse), 201)]
|
||||
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
|
||||
public async Task<IActionResult> Create([FromBody] RepositoryCloneRequest model, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> Create([FromBody] RepositoryCreateRequest model, CancellationToken cancellationToken)
|
||||
{
|
||||
if (model == null)
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[Route(SwarmConstants.ControllerRoute)]
|
||||
[Produces(MediaTypeNames.Application.Json)]
|
||||
[ApiController]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
public sealed class SwarmController : Controller
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a given <paramref name="model"/> has a valid <see cref="NamedEntity.Name"/> specified.
|
||||
/// Check if a given <paramref name="model"/> has a valid <see cref="UserName.Name"/> specified.
|
||||
/// </summary>
|
||||
/// <param name="model">The <see cref="UserUpdateRequest"/> to check.</param>
|
||||
/// <param name="newUser">If this is a new <see cref="UserResponse"/>.</param>
|
||||
@@ -105,7 +105,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Create a new <see cref="User"/>.
|
||||
/// </summary>
|
||||
/// <param name="model">The <see cref="UserResponse"/> to create.</param>
|
||||
/// <param name="model">The <see cref="UserCreateRequest"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation.</returns>
|
||||
/// <response code="201"><see cref="User"/> created successfully.</response>
|
||||
@@ -114,7 +114,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[TgsAuthorize(AdministrationRights.WriteUsers)]
|
||||
[ProducesResponseType(typeof(UserResponse), 201)]
|
||||
#pragma warning disable CA1502, CA1506
|
||||
public async Task<IActionResult> Create([FromBody] UserUpdateRequest model, CancellationToken cancellationToken)
|
||||
public async Task<IActionResult> Create([FromBody] UserCreateRequest model, CancellationToken cancellationToken)
|
||||
{
|
||||
if (model == null)
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
@@ -180,7 +180,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
Logger.LogInformation("Created new user {0} ({1})", dbUser.Name, dbUser.Id);
|
||||
|
||||
return Created(dbUser.ToApi(true));
|
||||
return Created(dbUser.ToApi());
|
||||
}
|
||||
#pragma warning restore CA1502, CA1506
|
||||
|
||||
@@ -191,11 +191,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation.</returns>
|
||||
/// <response code="200"><see cref="User"/> updated successfully.</response>
|
||||
/// <response code="200"><see cref="User"/> updated successfully. Not returned due to lack of permissions.</response>
|
||||
/// <response code="404">Requested <see cref="EntityId.Id"/> does not exist.</response>
|
||||
/// <response code="410">Requested <see cref="Api.Models.Internal.UserApiBase.Group"/> does not exist.</response>
|
||||
[HttpPost]
|
||||
[TgsAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnPassword | AdministrationRights.EditOwnOAuthConnections)]
|
||||
[ProducesResponseType(typeof(UserResponse), 200)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(typeof(ErrorMessageResponse), 404)]
|
||||
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
|
||||
#pragma warning disable CA1502 // TODO: Decomplexify
|
||||
@@ -334,14 +336,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
Logger.LogInformation("Updated user {0} ({1})", originalUser.Name, originalUser.Id);
|
||||
|
||||
// return id only if not a self update and cannot read users
|
||||
return Json(
|
||||
AuthenticationContext.User.Id == originalUser.Id
|
||||
|| callerAdministrationRights.HasFlag(AdministrationRights.ReadUsers)
|
||||
? originalUser.ToApi(true)
|
||||
: new UserResponse
|
||||
{
|
||||
Id = originalUser.Id
|
||||
});
|
||||
var canReadBack = AuthenticationContext.User.Id == originalUser.Id
|
||||
|| callerAdministrationRights.HasFlag(AdministrationRights.ReadUsers);
|
||||
return canReadBack
|
||||
? Json(originalUser.ToApi())
|
||||
: NoContent();
|
||||
}
|
||||
#pragma warning restore CA1506
|
||||
#pragma warning restore CA1502
|
||||
@@ -354,7 +353,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[HttpGet]
|
||||
[TgsAuthorize]
|
||||
[ProducesResponseType(typeof(UserResponse), 200)]
|
||||
public IActionResult Read() => Json(AuthenticationContext.User.ToApi(true));
|
||||
public IActionResult Read() => Json(AuthenticationContext.User.ToApi());
|
||||
|
||||
/// <summary>
|
||||
/// List all <see cref="User"/>s in the server.
|
||||
@@ -421,7 +420,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (user.CanonicalName == Models.User.CanonicalizeName(Models.User.TgsSystemUserName))
|
||||
return Forbid();
|
||||
|
||||
return Json(user.ToApi(true));
|
||||
return Json(user.ToApi());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Mime;
|
||||
using System.Reflection;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Controllers;
|
||||
@@ -120,12 +121,15 @@ namespace Tgstation.Server.Host.Core
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the OpenAPI schema ID for a given <paramref name="type"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The <see cref="Type"/> to generate a schema ID for.</param>
|
||||
/// <returns>The generated schema ID for <see cref="Type"/>.</returns>
|
||||
static string GenerateSchemaId(Type type)
|
||||
{
|
||||
if (type == typeof(NamedEntity))
|
||||
if (type == typeof(UserName))
|
||||
return "ShallowUserResponse";
|
||||
if (type == typeof(Api.Models.Internal.UserGroup))
|
||||
return "ShallowUserGroupResponse";
|
||||
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(PaginatedResponse<>))
|
||||
return $"Paginated{type.GenericTypeArguments.First().Name}";
|
||||
@@ -378,24 +382,12 @@ namespace Tgstation.Server.Host.Core
|
||||
Schema = productHeaderSchema
|
||||
});
|
||||
|
||||
var pathsToRemove = new List<string>();
|
||||
var filteredControllers = new string[]
|
||||
{
|
||||
nameof(BridgeController),
|
||||
nameof(ControlPanelController),
|
||||
nameof(SwarmController),
|
||||
};
|
||||
|
||||
var allSchemas = context
|
||||
.SchemaRepository
|
||||
.Schemas;
|
||||
foreach (var path in swaggerDoc.Paths)
|
||||
foreach (var operation in path.Value.Operations.Select(x => x.Value))
|
||||
{
|
||||
if (filteredControllers.Any(
|
||||
x => operation.OperationId.StartsWith(x, StringComparison.Ordinal)))
|
||||
{
|
||||
pathsToRemove.Add(path.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
operation.Parameters.Insert(0, new OpenApiParameter
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
@@ -415,12 +407,120 @@ namespace Tgstation.Server.Host.Core
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var filteredPath in pathsToRemove)
|
||||
swaggerDoc.Paths.Remove(filteredPath);
|
||||
|
||||
AddDefaultResponses(swaggerDoc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the <see cref="OpenApiSchema.Nullable"/>, <see cref="OpenApiSchema.ReadOnly"/>, and <see cref="OpenApiSchema.WriteOnly"/> to <see cref="OpenApiSchema.Properties"/> of a given <paramref name="rootSchema"/>.
|
||||
/// </summary>
|
||||
/// <param name="rootSchema">The root <see cref="OpenApiSchema"/>.</param>
|
||||
/// <param name="context">The current <see cref="SchemaFilterContext"/>.</param>
|
||||
static void ApplyAttributesForRootSchema(OpenApiSchema rootSchema, SchemaFilterContext context)
|
||||
{
|
||||
// tune up the descendants
|
||||
rootSchema.Nullable = false;
|
||||
var rootSchemaId = GenerateSchemaId(context.Type);
|
||||
var rootRequestSchema = rootSchemaId.EndsWith("Request", StringComparison.Ordinal);
|
||||
var rootResponseSchema = rootSchemaId.EndsWith("Response", StringComparison.Ordinal);
|
||||
var isPutRequest = rootRequestSchema && rootSchemaId.EndsWith("CreateRequest", StringComparison.Ordinal);
|
||||
|
||||
Tuple<PropertyInfo, string, OpenApiSchema, IDictionary<string, OpenApiSchema>> GetTypeFromKvp(Type currentType, KeyValuePair<string, OpenApiSchema> kvp, IDictionary<string, OpenApiSchema> schemaDictionary)
|
||||
{
|
||||
var propertyInfo = currentType
|
||||
.GetProperties()
|
||||
.Single(x => x.Name.Equals(kvp.Key, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return Tuple.Create(
|
||||
propertyInfo,
|
||||
kvp.Key,
|
||||
kvp.Value,
|
||||
schemaDictionary);
|
||||
}
|
||||
|
||||
var subSchemaStack = new Stack<Tuple<PropertyInfo, string, OpenApiSchema, IDictionary<string, OpenApiSchema>>>(
|
||||
rootSchema.Properties.Select(x => GetTypeFromKvp(context.Type, x, rootSchema.Properties)));
|
||||
|
||||
while (subSchemaStack.Count > 0)
|
||||
{
|
||||
var tuple = subSchemaStack.Pop();
|
||||
var subSchema = tuple.Item3;
|
||||
|
||||
if (subSchema.Reference != null)
|
||||
// can't mangle references per request
|
||||
continue;
|
||||
|
||||
var subSchemaPropertyInfo = tuple.Item1;
|
||||
|
||||
if (subSchema.Properties != null
|
||||
&& !subSchemaPropertyInfo
|
||||
.PropertyType
|
||||
.GetInterfaces()
|
||||
.Any(x => x == typeof(IEnumerable)))
|
||||
foreach (var kvp in subSchema.Properties)
|
||||
subSchemaStack.Push(GetTypeFromKvp(subSchemaPropertyInfo.PropertyType, kvp, subSchema.Properties));
|
||||
|
||||
var attributes = subSchemaPropertyInfo
|
||||
.GetCustomAttributes();
|
||||
var responsePresence = attributes
|
||||
.OfType<ResponseOptionsAttribute>()
|
||||
.FirstOrDefault()
|
||||
?.Presence
|
||||
?? FieldPresence.Required;
|
||||
var requestOptions = attributes
|
||||
.OfType<RequestOptionsAttribute>()
|
||||
.OrderBy(x => x.PutOnly) // Process PUTs last
|
||||
.ToList();
|
||||
|
||||
if (requestOptions.Any() && requestOptions.All(x => x.Presence == FieldPresence.Ignored && !x.PutOnly))
|
||||
subSchema.ReadOnly = true;
|
||||
|
||||
var subSchemaId = tuple.Item2;
|
||||
var subSchemaOwningDictionary = tuple.Item4;
|
||||
if (rootResponseSchema)
|
||||
{
|
||||
subSchema.Nullable = responsePresence == FieldPresence.Optional;
|
||||
if (responsePresence == FieldPresence.Ignored)
|
||||
subSchemaOwningDictionary.Remove(subSchemaId);
|
||||
}
|
||||
else if (rootRequestSchema)
|
||||
{
|
||||
subSchema.Nullable = true;
|
||||
var lastOptionWasIgnored = false;
|
||||
foreach (var requestOption in requestOptions)
|
||||
{
|
||||
var validForThisRequest = !requestOption.PutOnly || isPutRequest;
|
||||
if (!validForThisRequest)
|
||||
continue;
|
||||
|
||||
lastOptionWasIgnored = false;
|
||||
switch (requestOption.Presence)
|
||||
{
|
||||
case FieldPresence.Ignored:
|
||||
lastOptionWasIgnored = true;
|
||||
break;
|
||||
case FieldPresence.Optional:
|
||||
subSchema.Nullable = true;
|
||||
break;
|
||||
case FieldPresence.Required:
|
||||
subSchema.Nullable = false;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid FieldPresence: {requestOption.Presence}!");
|
||||
}
|
||||
}
|
||||
|
||||
if (lastOptionWasIgnored)
|
||||
subSchemaOwningDictionary.Remove(subSchemaId);
|
||||
}
|
||||
else if (responsePresence == FieldPresence.Required
|
||||
&& requestOptions.All(x => x.Presence == FieldPresence.Required && !x.PutOnly))
|
||||
subSchema.Nullable = false;
|
||||
|
||||
// otherwise, we have to assume it's a shared schema
|
||||
// use what Swagger thinks the nullability is by default
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
|
||||
{
|
||||
@@ -432,53 +532,18 @@ namespace Tgstation.Server.Host.Core
|
||||
// Nothing is required
|
||||
schema.Required.Clear();
|
||||
|
||||
// Could be nullable type, make sure to get the right one
|
||||
Type nonNullableType = context.Type.IsConstructedGenericType
|
||||
? context.Type.GenericTypeArguments.First()
|
||||
: context.Type;
|
||||
|
||||
if (context.MemberInfo != null)
|
||||
{
|
||||
var schemaId = GenerateSchemaId(context.MemberInfo.DeclaringType);
|
||||
var responseOptions = context.MemberInfo.GetCustomAttributes(typeof(ResponseOptionsAttribute), true).OfType<ResponseOptionsAttribute>().FirstOrDefault();
|
||||
var requestOptions = context.MemberInfo.GetCustomAttributes(typeof(RequestOptionsAttribute), true).OfType<RequestOptionsAttribute>().FirstOrDefault();
|
||||
|
||||
if (schemaId.EndsWith("Response", StringComparison.Ordinal))
|
||||
if (responseOptions?.Presence == FieldPresence.Optional
|
||||
|| (nonNullableType != context.Type
|
||||
&& !context.Type.GetInterfaces().Any(x => x == typeof(IEnumerable))))
|
||||
schema.Nullable = true;
|
||||
else if (responseOptions?.Presence == FieldPresence.Ignored)
|
||||
schema.WriteOnly = true;
|
||||
else
|
||||
schema.Nullable = false;
|
||||
else if (requestOptions != null)
|
||||
if (schemaId.EndsWith("Request", StringComparison.Ordinal))
|
||||
{
|
||||
var isPutRequest = schemaId.Contains("Create", StringComparison.OrdinalIgnoreCase);
|
||||
if (!(requestOptions.PutOnly && !isPutRequest))
|
||||
if (requestOptions.Presence == FieldPresence.Required)
|
||||
context.SchemaRepository.Schemas.Remove(schemaId);
|
||||
else if (requestOptions.Presence == FieldPresence.Ignored)
|
||||
schema.ReadOnly = true;
|
||||
}
|
||||
else if (responseOptions?.Presence != FieldPresence.Optional && requestOptions.Presence == FieldPresence.Required)
|
||||
// if something has request required and no optional response, it's always required
|
||||
schema.Nullable = false;
|
||||
else
|
||||
schema.Nullable = true;
|
||||
else
|
||||
{
|
||||
if (responseOptions?.Presence == FieldPresence.Ignored)
|
||||
context.SchemaRepository.Schemas.Remove(schemaId);
|
||||
schema.Nullable = true;
|
||||
}
|
||||
}
|
||||
if (context.MemberInfo == null)
|
||||
ApplyAttributesForRootSchema(schema, context);
|
||||
|
||||
if (!schema.Enum?.Any() ?? false)
|
||||
return;
|
||||
|
||||
OpenApiEnumVarNamesExtension.Apply(schema, nonNullableType);
|
||||
// Could be nullable type, make sure to get the right one
|
||||
Type firstGenericArgumentOrType = context.Type.IsConstructedGenericType
|
||||
? context.Type.GenericTypeArguments.First()
|
||||
: context.Type;
|
||||
|
||||
OpenApiEnumVarNamesExtension.Apply(schema, firstGenericArgumentOrType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -31,13 +31,13 @@ namespace Tgstation.Server.Host.Models
|
||||
StartedAt = StartedAt,
|
||||
StoppedAt = StoppedAt,
|
||||
Cancelled = Cancelled,
|
||||
CancelledBy = CancelledBy?.ToApi(false),
|
||||
CancelledBy = CancelledBy?.CreateUserName(),
|
||||
CancelRight = CancelRight,
|
||||
CancelRightsType = CancelRightsType,
|
||||
Description = Description,
|
||||
ExceptionDetails = ExceptionDetails,
|
||||
ErrorCode = ErrorCode,
|
||||
StartedBy = StartedBy.ToApi(false)
|
||||
StartedBy = StartedBy.CreateUserName()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Models
|
||||
TitleAtMerge = TitleAtMerge,
|
||||
Comment = Comment,
|
||||
Id = Id,
|
||||
MergedBy = MergedBy.ToApi(false),
|
||||
MergedBy = MergedBy.CreateUserName(),
|
||||
Number = Number,
|
||||
TargetCommitSha = TargetCommitSha,
|
||||
Url = Url
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Tgstation.Server.Host.Models
|
||||
public PermissionSet PermissionSet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The uppercase invariant of <see cref="NamedEntity.Name"/>
|
||||
/// The uppercase invariant of <see cref="UserName.Name"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
|
||||
@@ -67,43 +67,35 @@ namespace Tgstation.Server.Host.Models
|
||||
public ICollection<OAuthConnection> OAuthConnections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Change a <see cref="NamedEntity.Name"/> into a <see cref="CanonicalName"/>.
|
||||
/// Change a <see cref="UserName.Name"/> into a <see cref="CanonicalName"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The <see cref="NamedEntity.Name"/>.</param>
|
||||
/// <param name="name">The <see cref="UserName.Name"/>.</param>
|
||||
/// <returns>The <see cref="CanonicalName"/>.</returns>
|
||||
public static string CanonicalizeName(string name) => name?.ToUpperInvariant() ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="ToApi(bool)"/>
|
||||
/// Generate a <see cref="UserResponse"/> from <see langword="this"/>.
|
||||
/// </summary>
|
||||
/// <param name="recursive">If we should recurse on <see cref="CreatedBy"/></param>
|
||||
/// <param name="showDetails">If rights and system identifier should be shown</param>
|
||||
/// <param name="recursive">If we should recurse on <see cref="CreatedBy"/>.</param>
|
||||
/// <returns>A new <see cref="UserResponse"/>.</returns>
|
||||
UserResponse ToApi(bool recursive, bool showDetails) => new UserResponse
|
||||
UserResponse CreateUserResponse(bool recursive)
|
||||
{
|
||||
CreatedAt = showDetails ? CreatedAt : null,
|
||||
CreatedBy = showDetails && recursive ? CreatedBy?.ToApi(false, false) : null,
|
||||
Enabled = showDetails ? Enabled : null,
|
||||
Id = Id,
|
||||
Name = Name,
|
||||
SystemIdentifier = showDetails ? SystemIdentifier : null,
|
||||
OAuthConnections = showDetails
|
||||
? OAuthConnections
|
||||
?.Select(x => x.ToApi())
|
||||
.ToList()
|
||||
: null,
|
||||
Group = showDetails ? Group?.ToApi(false) : null,
|
||||
PermissionSet = showDetails ? PermissionSet?.ToApi() : null,
|
||||
};
|
||||
var result = CreateUserName<UserResponse>();
|
||||
if (recursive)
|
||||
result.CreatedBy = CreatedBy?.CreateUserName<UserName>();
|
||||
|
||||
/// <summary>
|
||||
/// Convert the <see cref="User"/> to it's API form
|
||||
/// </summary>
|
||||
/// <param name="showDetails">If system identifier, oauth connections, and group/permission set should be shown.</param>
|
||||
/// <returns>A new <see cref="UserResponse"/></returns>
|
||||
public UserResponse ToApi(bool showDetails) => ToApi(true, showDetails);
|
||||
result.CreatedAt = CreatedAt;
|
||||
result.Enabled = Enabled;
|
||||
result.SystemIdentifier = SystemIdentifier;
|
||||
result.OAuthConnections = OAuthConnections
|
||||
?.Select(x => x.ToApi())
|
||||
.ToList();
|
||||
result.Group = Group?.ToApi(false);
|
||||
result.PermissionSet = PermissionSet?.ToApi();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public UserResponse ToApi() => ToApi(true);
|
||||
public UserResponse ToApi() => CreateUserResponse(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +31,9 @@ namespace Tgstation.Server.Host.Models
|
||||
PermissionSet = PermissionSet.ToApi(),
|
||||
Users = showUsers
|
||||
? Users
|
||||
?.Select(x => x.ToApi(false))
|
||||
.OfType<NamedEntity>()
|
||||
?.Select(x => x.CreateUserName())
|
||||
.ToList()
|
||||
?? new List<NamedEntity>()
|
||||
?? new List<UserName>()
|
||||
: null,
|
||||
};
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
Assert.IsNull(initalRepo.ActiveJob);
|
||||
|
||||
const string Origin = "https://github.com/tgstation/tgstation-server";
|
||||
var cloneRequest = new RepositoryCloneRequest
|
||||
var cloneRequest = new RepositoryCreateRequest
|
||||
{
|
||||
Origin = new Uri(Origin),
|
||||
Reference = workingBranch,
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Tgstation.Server.Tests
|
||||
ChatBotLimit = 2
|
||||
}, cancellationToken);
|
||||
|
||||
static InstanceCreateRequest FromResponse(InstanceResponse response) => new InstanceCreateRequest
|
||||
static TRequestType FromResponse<TRequestType>(InstanceResponse response) where TRequestType : Api.Models.Instance, new() => new TRequestType
|
||||
{
|
||||
Id = response.Id,
|
||||
Path = response.Path,
|
||||
@@ -75,7 +75,7 @@ namespace Tgstation.Server.Tests
|
||||
Name = "NonEmptyTest"
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await Assert.ThrowsExceptionAsync<ConflictException>(() => instanceManagerClient.CreateOrAttach(FromResponse(firstTest), cancellationToken)).ConfigureAwait(false);
|
||||
await Assert.ThrowsExceptionAsync<ConflictException>(() => instanceManagerClient.CreateOrAttach(FromResponse<InstanceCreateRequest>(firstTest), cancellationToken)).ConfigureAwait(false);
|
||||
Assert.IsTrue(Directory.Exists(firstTest.Path));
|
||||
|
||||
//can't create instances in installation directory
|
||||
@@ -135,7 +135,7 @@ namespace Tgstation.Server.Tests
|
||||
//online it for real for component tests
|
||||
firstTest.Online = true;
|
||||
firstTest.ConfigurationType = ConfigurationType.HostWrite;
|
||||
firstTest = await instanceManagerClient.Update(FromResponse(firstTest), cancellationToken).ConfigureAwait(false);
|
||||
firstTest = await instanceManagerClient.Update(FromResponse<InstanceUpdateRequest>(firstTest), cancellationToken).ConfigureAwait(false);
|
||||
Assert.AreEqual(true, firstTest.Online);
|
||||
Assert.AreEqual(ConfigurationType.HostWrite, firstTest.ConfigurationType);
|
||||
Assert.IsTrue(Directory.Exists(firstTest.Path));
|
||||
@@ -175,7 +175,7 @@ namespace Tgstation.Server.Tests
|
||||
await ApiAssert.ThrowsException<ConflictException>(() => instanceManagerClient.Detach(firstTest, cancellationToken), ErrorCode.InstanceDetachOnline).ConfigureAwait(false);
|
||||
|
||||
firstTest.Online = false;
|
||||
firstTest = await instanceManagerClient.Update(FromResponse(firstTest), cancellationToken).ConfigureAwait(false);
|
||||
firstTest = await instanceManagerClient.Update(FromResponse<InstanceUpdateRequest>(firstTest), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await instanceManagerClient.Detach(firstTest, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -183,7 +183,7 @@ namespace Tgstation.Server.Tests
|
||||
Assert.IsTrue(File.Exists(attachPath));
|
||||
|
||||
//can recreate detached instance
|
||||
firstTest = await instanceManagerClient.CreateOrAttach(FromResponse(firstTest), cancellationToken).ConfigureAwait(false);
|
||||
firstTest = await instanceManagerClient.CreateOrAttach(FromResponse<InstanceCreateRequest>(firstTest), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Test updating only with SetChatBotLimit works
|
||||
var current = await usersClient.Read(cancellationToken);
|
||||
@@ -210,7 +210,7 @@ namespace Tgstation.Server.Tests
|
||||
//but only if the attach file exists
|
||||
await instanceManagerClient.Detach(firstTest, cancellationToken).ConfigureAwait(false);
|
||||
File.Delete(attachPath);
|
||||
await ApiAssert.ThrowsException<ConflictException>(() => instanceManagerClient.CreateOrAttach(FromResponse(firstTest), cancellationToken), ErrorCode.InstanceAtExistingPath).ConfigureAwait(false);
|
||||
await ApiAssert.ThrowsException<ConflictException>(() => instanceManagerClient.CreateOrAttach(FromResponse<InstanceCreateRequest>(firstTest), cancellationToken), ErrorCode.InstanceAtExistingPath).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user