diff --git a/src/Tgstation.Server.Host/Authority/Core/AuthorityInvoker{TAuthority}.cs b/src/Tgstation.Server.Host/Authority/Core/AuthorityInvoker{TAuthority}.cs
index 1756c62cf8..db3f06c803 100644
--- a/src/Tgstation.Server.Host/Authority/Core/AuthorityInvoker{TAuthority}.cs
+++ b/src/Tgstation.Server.Host/Authority/Core/AuthorityInvoker{TAuthority}.cs
@@ -1,9 +1,8 @@
using System;
+using System.Linq;
using System.Net;
using System.Threading.Tasks;
-using HotChocolate.Execution;
-
using Microsoft.AspNetCore.Mvc;
using Tgstation.Server.Host.Controllers;
using Tgstation.Server.Host.Extensions;
@@ -12,10 +11,7 @@ using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Authority.Core
{
- ///
- /// Invokes s.
- ///
- /// The invoked.
+ ///
sealed class AuthorityInvoker : IRestAuthorityInvoker, IGraphQLAuthorityInvoker
where TAuthority : IAuthority
{
@@ -25,7 +21,7 @@ namespace Tgstation.Server.Host.Authority.Core
readonly TAuthority authority;
///
- /// Throws a for errored s.
+ /// Throws a for errored s.
///
/// The potentially errored .
static void ThrowGraphQLErrorIfNecessary(AuthorityResponse authorityResponse)
@@ -49,16 +45,21 @@ namespace Tgstation.Server.Host.Authority.Core
///
public async ValueTask Invoke(ApiController controller, Func> authorityInvoker)
{
+ ArgumentNullException.ThrowIfNull(controller);
+ ArgumentNullException.ThrowIfNull(authorityInvoker);
+
var authorityResponse = await authorityInvoker(authority);
return CreateErroredActionResult(controller, authorityResponse) ?? controller.NoContent();
}
///
public async ValueTask InvokeTransformable(ApiController controller, Func>> authorityInvoker)
-
- where TResult : notnull, IApiTransformable
+ where TResult : notnull, ILegacyApiTransformable
where TApiModel : notnull
{
+ ArgumentNullException.ThrowIfNull(controller);
+ ArgumentNullException.ThrowIfNull(authorityInvoker);
+
var authorityResponse = await authorityInvoker(authority);
var erroredResult = CreateErroredActionResult(controller, authorityResponse);
if (erroredResult != null)
@@ -72,6 +73,9 @@ namespace Tgstation.Server.Host.Authority.Core
///
async ValueTask IRestAuthorityInvoker.Invoke(ApiController controller, Func>> authorityInvoker)
{
+ ArgumentNullException.ThrowIfNull(controller);
+ ArgumentNullException.ThrowIfNull(authorityInvoker);
+
var authorityResponse = await authorityInvoker(authority);
var erroredResult = CreateErroredActionResult(controller, authorityResponse);
if (erroredResult != null)
@@ -84,6 +88,8 @@ namespace Tgstation.Server.Host.Authority.Core
///
async ValueTask IGraphQLAuthorityInvoker.Invoke(Func> authorityInvoker)
{
+ ArgumentNullException.ThrowIfNull(authorityInvoker);
+
var authorityResponse = await authorityInvoker(authority);
ThrowGraphQLErrorIfNecessary(authorityResponse);
}
@@ -93,19 +99,42 @@ namespace Tgstation.Server.Host.Authority.Core
where TResult : TApiModel
where TApiModel : notnull
{
+ ArgumentNullException.ThrowIfNull(authorityInvoker);
+
var authorityResponse = await authorityInvoker(authority);
ThrowGraphQLErrorIfNecessary(authorityResponse);
return authorityResponse.Result!;
}
///
- async ValueTask IGraphQLAuthorityInvoker.InvokeTransformable(Func>> authorityInvoker)
+ async ValueTask IGraphQLAuthorityInvoker.InvokeTransformable(Func>> authorityInvoker)
{
+ ArgumentNullException.ThrowIfNull(authorityInvoker);
+
var authorityResponse = await authorityInvoker(authority);
ThrowGraphQLErrorIfNecessary(authorityResponse);
return authorityResponse.Result!.ToApi();
}
+ ///
+ public IQueryable InvokeQueryable(Func> authorityInvoker)
+ {
+ ArgumentNullException.ThrowIfNull(authorityInvoker);
+ return authorityInvoker(authority);
+ }
+
+ ///
+ public IQueryable InvokeTransformableQueryable(Func> authorityInvoker)
+ where TResult : IApiTransformable
+ where TApiModel : notnull
+ where TTransformer : ITransformer, new()
+ {
+ ArgumentNullException.ThrowIfNull(authorityInvoker);
+ var expression = new TTransformer().Expression;
+ return authorityInvoker(authority)
+ .Select(expression);
+ }
+
///
/// Create an for a given if it is erroring.
///
diff --git a/src/Tgstation.Server.Host/Authority/Core/IAuthorityInvoker{TAuthority}.cs b/src/Tgstation.Server.Host/Authority/Core/IAuthorityInvoker{TAuthority}.cs
new file mode 100644
index 0000000000..9b49bd9dda
--- /dev/null
+++ b/src/Tgstation.Server.Host/Authority/Core/IAuthorityInvoker{TAuthority}.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Linq;
+
+using Tgstation.Server.Host.Models;
+
+namespace Tgstation.Server.Host.Authority.Core
+{
+ ///
+ /// Invokes s.
+ ///
+ /// The invoked.
+ public interface IAuthorityInvoker
+ where TAuthority : IAuthority
+ {
+ ///
+ /// Invoke a method and get the result.
+ ///
+ /// The returned .
+ /// The returning a .
+ /// A returned.
+ IQueryable InvokeQueryable(Func> authorityInvoker);
+
+ ///
+ /// Invoke a method and get the transformed result.
+ ///
+ /// The returned by the .
+ /// The returned .
+ /// The for converting s to s.
+ /// The returning a .
+ /// A returned.
+ IQueryable InvokeTransformableQueryable(Func> authorityInvoker)
+ where TResult : IApiTransformable
+ where TApiModel : notnull
+ where TTransformer : ITransformer, new();
+ }
+}
diff --git a/src/Tgstation.Server.Host/Authority/Core/IGraphQLAuthorityInvoker{TAuthority}.cs b/src/Tgstation.Server.Host/Authority/Core/IGraphQLAuthorityInvoker{TAuthority}.cs
index 7ce01fa637..1c5031edc3 100644
--- a/src/Tgstation.Server.Host/Authority/Core/IGraphQLAuthorityInvoker{TAuthority}.cs
+++ b/src/Tgstation.Server.Host/Authority/Core/IGraphQLAuthorityInvoker{TAuthority}.cs
@@ -9,7 +9,7 @@ namespace Tgstation.Server.Host.Authority.Core
/// Invokes s from GraphQL endpoints.
///
/// The invoked.
- public interface IGraphQLAuthorityInvoker
+ public interface IGraphQLAuthorityInvoker : IAuthorityInvoker
where TAuthority : IAuthority
{
///
@@ -35,10 +35,12 @@ namespace Tgstation.Server.Host.Authority.Core
///
/// The .
/// The resulting of the return value.
+ /// The for converting s to s.
/// The returning a resulting in the .
/// A resulting in the generated for the resulting .
- ValueTask InvokeTransformable(Func>> authorityInvoker)
- where TResult : notnull, IApiTransformable
- where TApiModel : notnull;
+ ValueTask InvokeTransformable(Func>> authorityInvoker)
+ where TResult : notnull, IApiTransformable
+ where TApiModel : notnull
+ where TTransformer : ITransformer, new();
}
}
diff --git a/src/Tgstation.Server.Host/Authority/Core/IRestAuthorityInvoker{TAuthority}.cs b/src/Tgstation.Server.Host/Authority/Core/IRestAuthorityInvoker{TAuthority}.cs
index 55e61f34cf..564de05c2a 100644
--- a/src/Tgstation.Server.Host/Authority/Core/IRestAuthorityInvoker{TAuthority}.cs
+++ b/src/Tgstation.Server.Host/Authority/Core/IRestAuthorityInvoker{TAuthority}.cs
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Authority.Core
/// Invokes methods and generates responses.
///
/// The type of .
- public interface IRestAuthorityInvoker
+ public interface IRestAuthorityInvoker : IAuthorityInvoker
where TAuthority : IAuthority
{
///
@@ -43,7 +43,7 @@ namespace Tgstation.Server.Host.Authority.Core
/// The returning a resulting in the .
/// A resulting in the generated for the resulting .
ValueTask InvokeTransformable(ApiController controller, Func>> authorityInvoker)
- where TResult : notnull, IApiTransformable
+ where TResult : notnull, ILegacyApiTransformable
where TApiModel : notnull;
}
}
diff --git a/src/Tgstation.Server.Host/Authority/IUserAuthority.cs b/src/Tgstation.Server.Host/Authority/IUserAuthority.cs
index 71f807371a..dfc9cdda9d 100644
--- a/src/Tgstation.Server.Host/Authority/IUserAuthority.cs
+++ b/src/Tgstation.Server.Host/Authority/IUserAuthority.cs
@@ -38,8 +38,8 @@ namespace Tgstation.Server.Host.Authority
/// Gets all registered s.
///
/// If related entities should be loaded.
- /// A resulting in a .
+ /// A of s.
[TgsAuthorize(AdministrationRights.ReadUsers)]
- public ValueTask>> List(bool includeJoins);
+ public IQueryable Queryable(bool includeJoins);
}
}
diff --git a/src/Tgstation.Server.Host/Authority/UserAuthority.cs b/src/Tgstation.Server.Host/Authority/UserAuthority.cs
index fe15593a1b..ddb20d4108 100644
--- a/src/Tgstation.Server.Host/Authority/UserAuthority.cs
+++ b/src/Tgstation.Server.Host/Authority/UserAuthority.cs
@@ -1,8 +1,11 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using GreenDonut;
+
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@@ -21,24 +24,55 @@ namespace Tgstation.Server.Host.Authority
///
readonly IDatabaseContext databaseContext;
+ ///
+ /// The for the .
+ ///
+ readonly IUsersDataLoader dataLoader;
+
///
/// The for the .
///
readonly IAuthenticationContext authenticationContext;
+ ///
+ /// Implements the .
+ ///
+ /// The of s to load.
+ /// The to load from.
+ /// The for the operation.
+ /// A resulting in a of the requested s.
+ [DataLoader]
+ public static async ValueTask> GetUsers(
+ IReadOnlyList ids,
+ IDatabaseContext databaseContext,
+ CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(ids);
+ ArgumentNullException.ThrowIfNull(databaseContext);
+
+ return await databaseContext
+ .Users
+ .AsQueryable()
+ .Where(x => ids.Contains(x.Id!.Value))
+ .ToDictionaryAsync(user => user.Id!.Value, cancellationToken);
+ }
+
///
/// Initializes a new instance of the class.
///
/// The to use.
/// The value of .
+ /// The value of .
/// The value of .
public UserAuthority(
ILogger logger,
IDatabaseContext databaseContext,
+ IUsersDataLoader dataLoader,
IAuthenticationContext authenticationContext)
: base(logger)
{
this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext));
+ this.dataLoader = dataLoader ?? throw new ArgumentNullException(nameof(dataLoader));
this.authenticationContext = authenticationContext ?? throw new ArgumentNullException(nameof(authenticationContext));
}
@@ -49,11 +83,18 @@ namespace Tgstation.Server.Host.Authority
///
public async ValueTask> GetId(long id, bool includeJoins, bool allowSystemUser, CancellationToken cancellationToken)
{
- var queryable = ListCore(includeJoins);
+ User? user;
+ if (includeJoins)
+ {
+ var queryable = Queryable(true, true);
+
+ user = await queryable.FirstOrDefaultAsync(
+ dbModel => dbModel.Id == id,
+ cancellationToken);
+ }
+ else
+ user = await dataLoader.LoadAsync(id, cancellationToken);
- var user = await queryable.FirstOrDefaultAsync(
- dbModel => dbModel.Id == id,
- cancellationToken);
if (user == default)
return NotFound();
@@ -64,26 +105,26 @@ namespace Tgstation.Server.Host.Authority
}
///
- public ValueTask>> List(bool includeJoins)
- {
- var systemUserCanonicalName = User.CanonicalizeName(User.TgsSystemUserName);
- return ValueTask.FromResult(
- new AuthorityResponse>(
- ListCore(includeJoins)
- .Where(x => x.CanonicalName != systemUserCanonicalName)));
- }
+ public IQueryable Queryable(bool includeJoins)
+ => Queryable(includeJoins, false);
///
- /// Generates an for listing s.
+ /// Gets all registered s.
///
/// If related entities should be loaded.
- /// A new of s.
- private IQueryable ListCore(bool includeJoins)
+ /// If the with the should be included in results.
+ /// A of s.
+ IQueryable Queryable(bool includeJoins, bool allowSystemUser)
{
+ var tgsUserCanonicalName = User.CanonicalizeName(User.TgsSystemUserName);
var queryable = databaseContext
.Users
.AsQueryable();
+ if (!allowSystemUser)
+ queryable = queryable
+ .Where(user => user.CanonicalName != tgsUserCanonicalName);
+
if (includeJoins)
queryable = queryable
.Include(x => x.CreatedBy)
diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs
index 709dfa7087..439b3e3c07 100644
--- a/src/Tgstation.Server.Host/Controllers/ApiController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs
@@ -288,7 +288,7 @@ namespace Tgstation.Server.Host.Controllers
int? pageQuery,
int? pageSizeQuery,
CancellationToken cancellationToken)
- where TModel : IApiTransformable
+ where TModel : ILegacyApiTransformable
=> PaginatedImpl(
queryGenerator,
resultTransformer,
@@ -299,7 +299,7 @@ namespace Tgstation.Server.Host.Controllers
///
/// Generates a paginated response.
///
- /// The of model being generated. If different from , must implement for .
+ /// The of model being generated. If different from , must implement for .
/// The of model being returned.
/// A resulting in a resulting in the generated .
/// A to transform the s after being queried.
@@ -356,7 +356,7 @@ namespace Tgstation.Server.Host.Controllers
finalResults = (List)(object)pagedResults; // clearly a safe cast
else
finalResults = pagedResults
- .OfType>()
+ .OfType>()
.Select(x => x.ToApi())
.ToList();
diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs
index e59df1f889..0965ad26b5 100644
--- a/src/Tgstation.Server.Host/Controllers/UserController.cs
+++ b/src/Tgstation.Server.Host/Controllers/UserController.cs
@@ -371,21 +371,14 @@ namespace Tgstation.Server.Host.Controllers
/// A resulting in the of the operation.
/// Retrieved s successfully.
[HttpGet(Routes.List)]
- [TgsAuthorize(AdministrationRights.ReadUsers)]
+ [TgsRestAuthorize(nameof(IUserAuthority.Queryable))]
[ProducesResponseType(typeof(PaginatedResponse), 200)]
public ValueTask List([FromQuery] int? page, [FromQuery] int? pageSize, CancellationToken cancellationToken)
=> Paginated(
() => ValueTask.FromResult(
new PaginatableResult(
- DatabaseContext
- .Users
- .AsQueryable()
- .Where(x => x.CanonicalName != Models.User.CanonicalizeName(Models.User.TgsSystemUserName))
- .Include(x => x.CreatedBy)
- .Include(x => x.PermissionSet)
- .Include(x => x.OAuthConnections)
- .Include(x => x.Group!)
- .ThenInclude(x => x.PermissionSet)
+ userAuthority.InvokeQueryable(
+ authority => authority.Queryable(true))
.OrderBy(x => x.Id))),
null,
page,
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index 41d20aba8e..1b07bbb9c5 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -297,10 +297,19 @@ namespace Tgstation.Server.Host.Core
.AddGraphQLServer()
.AddAuthorization()
.AddMutationConventions()
+ .AddGlobalObjectIdentification()
.ModifyOptions(options =>
{
options.EnableDefer = true;
})
+ .ModifyPagingOptions(pagingOptions =>
+ {
+ pagingOptions.IncludeTotalCount = true;
+ pagingOptions.RequirePagingBoundaries = false;
+ })
+ .AddFiltering()
+ .AddSorting()
+ .AddHostTypes()
.AddErrorFilter()
.AddType()
.AddType()
diff --git a/src/Tgstation.Server.Host/GraphQL/Interfaces/IGateway.cs b/src/Tgstation.Server.Host/GraphQL/Interfaces/IGateway.cs
index 89acbc10cc..e8846abbea 100644
--- a/src/Tgstation.Server.Host/GraphQL/Interfaces/IGateway.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Interfaces/IGateway.cs
@@ -11,7 +11,7 @@ using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.GraphQL.Interfaces
{
///
- /// Management interface for the parent .
+ /// Management interface for the parent .
///
public interface IGateway
{
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/Entity.cs b/src/Tgstation.Server.Host/GraphQL/Types/Entity.cs
index ab51d61a6f..4f45c22306 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/Entity.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/Entity.cs
@@ -1,4 +1,6 @@
-using HotChocolate.Types.Relay;
+using System.Diagnostics.CodeAnalysis;
+
+using HotChocolate.Types.Relay;
namespace Tgstation.Server.Host.GraphQL.Types
{
@@ -11,12 +13,20 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// The ID of the .
///
[ID]
- public long Id { get; }
+ public required long Id { get; init; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected Entity()
+ {
+ }
///
/// Initializes a new instance of the class.
///
/// The value of .
+ [SetsRequiredMembers]
protected Entity(long id)
{
Id = id;
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/LocalGateway.cs b/src/Tgstation.Server.Host/GraphQL/Types/LocalGateway.cs
index 1774bc6596..107df473e1 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/LocalGateway.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/LocalGateway.cs
@@ -13,7 +13,7 @@ using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.GraphQL.Types
{
///
- /// for the this query is executing on.
+ /// for the this query is executing on.
///
public sealed class LocalGateway : IGateway
{
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/NamedEntity.cs b/src/Tgstation.Server.Host/GraphQL/Types/NamedEntity.cs
index a52677b834..d0a36221cc 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/NamedEntity.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/NamedEntity.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using Tgstation.Server.Host.GraphQL.Interfaces;
@@ -12,12 +13,20 @@ namespace Tgstation.Server.Host.GraphQL.Types
///
/// The name of the .
///
- public string Name { get; }
+ public required string Name { get; init; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected NamedEntity()
+ {
+ }
///
/// Initializes a new instance of the class.
///
/// The to copy.
+ [SetsRequiredMembers]
protected NamedEntity(NamedEntity copy)
: base(copy?.Id ?? throw new ArgumentNullException(nameof(copy)))
{
@@ -29,6 +38,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
///
/// The ID for the .
/// The value of .
+ [SetsRequiredMembers]
protected NamedEntity(long id, string name)
: base(id)
{
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/NodeInformation.cs b/src/Tgstation.Server.Host/GraphQL/Types/NodeInformation.cs
index 0661bf92c9..546d77d9f2 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/NodeInformation.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/NodeInformation.cs
@@ -1,14 +1,34 @@
using System;
+using System.Linq;
+using HotChocolate;
using HotChocolate.Types.Relay;
+using Tgstation.Server.Host.Swarm;
+
namespace Tgstation.Server.Host.GraphQL.Types
{
///
/// Represent a server in the TGS server swarm.
///
+ [Node]
public sealed class NodeInformation
{
+ public NodeInformation? GetNodeInformation(
+ string identifier,
+ [Service] ISwarmService swarmService)
+ {
+ ArgumentNullException.ThrowIfNull(identifier);
+ ArgumentNullException.ThrowIfNull(swarmService);
+
+ var node = swarmService.GetSwarmServers()
+ ?.FirstOrDefault(node => node.Identifier == identifier);
+ if (node == null)
+ return null;
+
+ return new NodeInformation(node);
+ }
+
///
/// The swarm server ID.
///
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/PermissionSet.cs b/src/Tgstation.Server.Host/GraphQL/Types/PermissionSet.cs
index 6d7b0d6b5f..9f0734f316 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/PermissionSet.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/PermissionSet.cs
@@ -1,10 +1,15 @@
-using Tgstation.Server.Api.Rights;
+using System.Diagnostics.CodeAnalysis;
+
+using HotChocolate.Types.Relay;
+
+using Tgstation.Server.Api.Rights;
namespace Tgstation.Server.Host.GraphQL.Types
{
///
/// Represents a set of permissions for the server.
///
+ [Node]
public sealed class PermissionSet : Entity
{
///
@@ -23,6 +28,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// The .
/// The value of .
/// The value of .
+ [SetsRequiredMembers]
public PermissionSet(long id, AdministrationRights administrationRights, InstanceManagerRights instanceManagerRights)
: base(id)
{
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/RemoteGateway.cs b/src/Tgstation.Server.Host/GraphQL/Types/RemoteGateway.cs
index 0a00849057..5c3305e56d 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/RemoteGateway.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/RemoteGateway.cs
@@ -14,7 +14,7 @@ using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.GraphQL.Types
{
///
- /// for accessing remote s.
+ /// for accessing remote s.
///
/// This is currently unimplemented.
public sealed class RemoteGateway : IGateway
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/ServerSwarm.cs b/src/Tgstation.Server.Host/GraphQL/Types/ServerSwarm.cs
index e43d9c5b2e..1b874c13b1 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/ServerSwarm.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/ServerSwarm.cs
@@ -40,12 +40,12 @@ namespace Tgstation.Server.Host.GraphQL.Types
public Users Users() => new();
///
- /// Gets the connected server.
+ /// Gets the connected server.
///
/// The to use.
/// The containing the current .
- /// A new .
- public Node CurrentNode(
+ /// A new .
+ public SwarmNode CurrentNode(
[Service] ISwarmService swarmService,
[Service] IOptionsSnapshot swarmConfigurationOptions)
{
@@ -56,19 +56,19 @@ namespace Tgstation.Server.Host.GraphQL.Types
if (nodeInfos != null)
return nodeInfos.First(x => x.Info!.Identifier == swarmConfigurationOptions.Value.Identifier);
- return new Node(null);
+ return new SwarmNode(null);
}
///
- /// Gets all servers in the swarm.
+ /// Gets all servers in the swarm.
///
/// The to use.
- /// A of s if the local server is part of a swarm, otherwise.
- public List? Nodes(
+ /// A of s if the local server is part of a swarm, otherwise.
+ public List? Nodes(
[Service] ISwarmService swarmService)
{
ArgumentNullException.ThrowIfNull(swarmService);
- return swarmService.GetSwarmServers()?.Select(x => new Node(new NodeInformation(x))).ToList();
+ return swarmService.GetSwarmServers()?.Select(x => new SwarmNode(new NodeInformation(x))).ToList();
}
}
}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/Node.cs b/src/Tgstation.Server.Host/GraphQL/Types/SwarmNode.cs
similarity index 79%
rename from src/Tgstation.Server.Host/GraphQL/Types/Node.cs
rename to src/Tgstation.Server.Host/GraphQL/Types/SwarmNode.cs
index f42183950d..8558256247 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/Node.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/SwarmNode.cs
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
///
/// Represents a node server in a swarm.
///
- public sealed class Node
+ public sealed class SwarmNode
{
///
/// Gets the .
@@ -20,20 +20,20 @@ namespace Tgstation.Server.Host.GraphQL.Types
public NodeInformation? Info { get; }
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The value of .
- public Node(NodeInformation? info)
+ public SwarmNode(NodeInformation? info)
{
Info = info;
}
///
- /// Gets the 's .
+ /// Gets the 's .
///
/// The containing the current .
/// A new .
- /// The 's .
+ /// The 's .
public IGateway? Gateway([Service] IOptionsSnapshot swarmConfigurationOptions)
{
ArgumentNullException.ThrowIfNull(swarmConfigurationOptions);
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/User.cs b/src/Tgstation.Server.Host/GraphQL/Types/User.cs
index 4ab07675df..efacb5525e 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/User.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/User.cs
@@ -4,76 +4,61 @@ using System.Threading;
using System.Threading.Tasks;
using HotChocolate;
+using HotChocolate.Types.Relay;
using Tgstation.Server.Host.Authority;
using Tgstation.Server.Host.Authority.Core;
using Tgstation.Server.Host.GraphQL.Interfaces;
+using Tgstation.Server.Host.Models.Transformers;
namespace Tgstation.Server.Host.GraphQL.Types
{
///
/// A user registered in the server.
///
+ [Node]
public sealed class User : NamedEntity, IUserName
{
///
/// If the is enabled since users cannot be deleted. System users cannot be disabled.
///
- public bool Enabled { get; }
+ public required bool Enabled { get; init; }
///
/// The user's canonical (Uppercase) name.
///
- public string CanonicalName { get; }
+ public required string CanonicalName { get; init; }
///
/// When the was created.
///
- public DateTimeOffset CreatedAt { get; }
+ public required DateTimeOffset CreatedAt { get; init; }
///
/// The SID/UID of the on Windows/POSIX respectively.
///
- public string? SystemIdentifier { get; }
+ public required string? SystemIdentifier { get; init; }
///
/// The of the .
///
- readonly long? createdById;
+ [GraphQLIgnore]
+ public required long? CreatedById { get; init; }
///
/// The of the .
///
- readonly long? groupId;
+ [GraphQLIgnore]
+ public required long? GroupId { get; init; }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The .
- /// The .
- /// The value of .
- /// The value of .
- /// The value of .
- /// The value of .
- /// The value of .
- /// The value of .
- public User(
+ public static ValueTask GetUser(
long id,
- string name,
- string canonicalName,
- string? systemIdentifier,
- DateTimeOffset createdAt,
- long? createdById,
- long? groupId,
- bool enabled)
- : base(id, name)
+ [Service] IGraphQLAuthorityInvoker authorityInvoker,
+ CancellationToken cancellationToken)
{
- SystemIdentifier = systemIdentifier;
- CanonicalName = canonicalName ?? throw new ArgumentNullException(nameof(canonicalName));
- CreatedAt = createdAt;
- this.createdById = createdById;
- Enabled = enabled;
- this.groupId = groupId;
+ ArgumentNullException.ThrowIfNull(authorityInvoker);
+ return authorityInvoker.InvokeTransformable(
+ authority => authority.GetId(id, false, false, cancellationToken));
}
///
@@ -87,10 +72,10 @@ namespace Tgstation.Server.Host.GraphQL.Types
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(userAuthority);
- if (!createdById.HasValue)
+ if (!CreatedById.HasValue)
return null;
- var user = await userAuthority.InvokeTransformable(authority => authority.GetId(createdById.Value, false, true, cancellationToken));
+ var user = await userAuthority.InvokeTransformable(authority => authority.GetId(CreatedById.Value, false, true, cancellationToken));
if (user.CanonicalName == Models.User.CanonicalizeName(Models.User.TgsSystemUserName))
return new UserName(user);
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/UserGroup.cs b/src/Tgstation.Server.Host/GraphQL/Types/UserGroup.cs
index 82b6242221..7a647e0b88 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/UserGroup.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/UserGroup.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using HotChocolate.Types;
@@ -22,6 +23,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// The .
/// The .
/// The value of .
+ [SetsRequiredMembers]
public UserGroup(
long id,
string name,
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/UserName.cs b/src/Tgstation.Server.Host/GraphQL/Types/UserName.cs
index f16379debc..3379741f91 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/UserName.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/UserName.cs
@@ -1,16 +1,41 @@
-using Tgstation.Server.Host.GraphQL.Interfaces;
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+
+using HotChocolate;
+using HotChocolate.Types.Relay;
+
+using Tgstation.Server.Host.Authority;
+using Tgstation.Server.Host.Authority.Core;
+using Tgstation.Server.Host.GraphQL.Interfaces;
+using Tgstation.Server.Host.Models.Transformers;
namespace Tgstation.Server.Host.GraphQL.Types
{
///
/// A with limited fields.
///
+ [Node]
public sealed class UserName : NamedEntity, IUserName
{
+ public static async ValueTask GetUserName(
+ long id,
+ [Service] IGraphQLAuthorityInvoker authorityInvoker,
+ CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(authorityInvoker);
+ var user = await authorityInvoker.InvokeTransformable(
+ authority => authority.GetId(id, false, true, cancellationToken));
+
+ return new UserName(user);
+ }
+
///
/// Initializes a new instance of the class.
///
/// The to copy.
+ [SetsRequiredMembers]
public UserName(NamedEntity copy)
: base(copy)
{
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/Users.cs b/src/Tgstation.Server.Host/GraphQL/Types/Users.cs
index 754771e917..fcba0f00ea 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/Users.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/Users.cs
@@ -4,12 +4,13 @@ using System.Threading;
using System.Threading.Tasks;
using HotChocolate;
+using HotChocolate.Data;
using HotChocolate.Types;
using HotChocolate.Types.Relay;
using Tgstation.Server.Host.Authority;
using Tgstation.Server.Host.Authority.Core;
-using Tgstation.Server.Host.Models;
+using Tgstation.Server.Host.Models.Transformers;
using Tgstation.Server.Host.Security;
#pragma warning disable CA1724 // conflict with GitLabApiClient.Models.Users. They can fuck off
@@ -33,7 +34,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(userAuthority);
- return userAuthority.InvokeTransformable(authority => authority.Read(cancellationToken));
+ return userAuthority.InvokeTransformable(authority => authority.Read(cancellationToken));
}
///
@@ -49,26 +50,23 @@ namespace Tgstation.Server.Host.GraphQL.Types
[ID(nameof(User))] long id,
[Service] IGraphQLAuthorityInvoker userAuthority,
CancellationToken cancellationToken)
- {
- ArgumentNullException.ThrowIfNull(userAuthority);
- return await userAuthority.InvokeTransformable(authority => authority.GetId(id, false, false, cancellationToken));
- }
+ => await User.GetUser(id, userAuthority, cancellationToken);
///
/// Lists all registered s.
///
/// The .
/// A list of all registered s.
- [UsePaging(IncludeTotalCount = true)]
- [TgsGraphQLAuthorize(nameof(IUserAuthority.List))]
- public async ValueTask> List(
+ [UsePaging]
+ [UseFiltering]
+ [UseSorting]
+ [TgsGraphQLAuthorize(nameof(IUserAuthority.Queryable))]
+ public IQueryable? Queryable(
[Service] IGraphQLAuthorityInvoker userAuthority)
{
ArgumentNullException.ThrowIfNull(userAuthority);
- var dtoQueryable = await userAuthority.Invoke, IQueryable>(authority => authority.List(false));
- return dtoQueryable
- .Cast>()
- .Select(dto => dto.ToApi());
+ var dtoQueryable = userAuthority.InvokeTransformableQueryable(authority => authority.Queryable(false));
+ return dtoQueryable;
}
}
}
diff --git a/src/Tgstation.Server.Host/Models/ChatBot.cs b/src/Tgstation.Server.Host/Models/ChatBot.cs
index caf1f0c645..908581eff5 100644
--- a/src/Tgstation.Server.Host/Models/ChatBot.cs
+++ b/src/Tgstation.Server.Host/Models/ChatBot.cs
@@ -8,7 +8,7 @@ using Tgstation.Server.Api.Models.Response;
namespace Tgstation.Server.Host.Models
{
///
- public sealed class ChatBot : Api.Models.Internal.ChatBotSettings, IApiTransformable
+ public sealed class ChatBot : Api.Models.Internal.ChatBotSettings, ILegacyApiTransformable
{
///
/// Default for .
diff --git a/src/Tgstation.Server.Host/Models/CompileJob.cs b/src/Tgstation.Server.Host/Models/CompileJob.cs
index 1ce860be6e..d316f18bd3 100644
--- a/src/Tgstation.Server.Host/Models/CompileJob.cs
+++ b/src/Tgstation.Server.Host/Models/CompileJob.cs
@@ -7,7 +7,7 @@ using Tgstation.Server.Api.Models.Response;
namespace Tgstation.Server.Host.Models
{
///
- public sealed class CompileJob : Api.Models.Internal.CompileJob, IApiTransformable
+ public sealed class CompileJob : Api.Models.Internal.CompileJob, ILegacyApiTransformable
{
///
/// See .
diff --git a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs
index 5e55dc9683..3904c8ad5d 100644
--- a/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs
+++ b/src/Tgstation.Server.Host/Models/DreamMakerSettings.cs
@@ -5,7 +5,7 @@ using Tgstation.Server.Api.Models.Response;
namespace Tgstation.Server.Host.Models
{
///
- public sealed class DreamMakerSettings : Api.Models.Internal.DreamMakerSettings, IApiTransformable
+ public sealed class DreamMakerSettings : Api.Models.Internal.DreamMakerSettings, ILegacyApiTransformable
{
///
/// The row Id.
diff --git a/src/Tgstation.Server.Host/Models/IApiTransformable{TModel,TApiModel,TTransformer}.cs b/src/Tgstation.Server.Host/Models/IApiTransformable{TModel,TApiModel,TTransformer}.cs
new file mode 100644
index 0000000000..df45b7d7e8
--- /dev/null
+++ b/src/Tgstation.Server.Host/Models/IApiTransformable{TModel,TApiModel,TTransformer}.cs
@@ -0,0 +1,25 @@
+using System;
+
+#pragma warning disable CA1005
+
+namespace Tgstation.Server.Host.Models
+{
+ ///
+ /// Represents a host-side model that may be transformed into a .
+ ///
+ /// The internal model .
+ /// The API model .
+ /// The .
+ public interface IApiTransformable
+ where TModel : IApiTransformable
+ where TTransformer : ITransformer, new()
+ {
+ ///
+ /// Convert the to it's .
+ ///
+ /// A new based on the .
+ TApiModel ToApi()
+ => new TTransformer()
+ .CompiledExpression((TModel)this);
+ }
+}
diff --git a/src/Tgstation.Server.Host/Models/IApiTransformable.cs b/src/Tgstation.Server.Host/Models/ILegacyApiTransformable{TApiModel}.cs
similarity index 61%
rename from src/Tgstation.Server.Host/Models/IApiTransformable.cs
rename to src/Tgstation.Server.Host/Models/ILegacyApiTransformable{TApiModel}.cs
index e2b807b62e..b8064202d8 100644
--- a/src/Tgstation.Server.Host/Models/IApiTransformable.cs
+++ b/src/Tgstation.Server.Host/Models/ILegacyApiTransformable{TApiModel}.cs
@@ -4,12 +4,12 @@
/// Represents a host-side model that may be transformed into a .
///
/// The API form of the model.
- public interface IApiTransformable
+ public interface ILegacyApiTransformable
{
///
- /// Convert the to it's .
+ /// Convert the to it's .
///
- /// A new based on the .
+ /// A new based on the .
TApiModel ToApi();
}
}
diff --git a/src/Tgstation.Server.Host/Models/ITransformer{TInput,TOutput}.cs b/src/Tgstation.Server.Host/Models/ITransformer{TInput,TOutput}.cs
new file mode 100644
index 0000000000..9da15bcfb7
--- /dev/null
+++ b/src/Tgstation.Server.Host/Models/ITransformer{TInput,TOutput}.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Tgstation.Server.Host.Models
+{
+ ///
+ /// Contains a transformation for converting s to s.
+ ///
+ /// The input .
+ /// The output .
+ public interface ITransformer
+ {
+ ///
+ /// form of the transformation.
+ ///
+ Expression> Expression { get; }
+
+ ///
+ /// The compiled .
+ ///
+ Func CompiledExpression { get; }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs
index 35434d81e1..ded8b8fd55 100644
--- a/src/Tgstation.Server.Host/Models/Instance.cs
+++ b/src/Tgstation.Server.Host/Models/Instance.cs
@@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Models
///
/// Represents an in the database.
///
- public sealed class Instance : Api.Models.Instance, IApiTransformable
+ public sealed class Instance : Api.Models.Instance, ILegacyApiTransformable
{
///
/// Default for .
diff --git a/src/Tgstation.Server.Host/Models/InstancePermissionSet.cs b/src/Tgstation.Server.Host/Models/InstancePermissionSet.cs
index 8db1f17fba..be4e5357d1 100644
--- a/src/Tgstation.Server.Host/Models/InstancePermissionSet.cs
+++ b/src/Tgstation.Server.Host/Models/InstancePermissionSet.cs
@@ -5,7 +5,7 @@ using Tgstation.Server.Api.Models.Response;
namespace Tgstation.Server.Host.Models
{
///
- public sealed class InstancePermissionSet : Api.Models.Internal.InstancePermissionSet, IApiTransformable
+ public sealed class InstancePermissionSet : Api.Models.Internal.InstancePermissionSet, ILegacyApiTransformable
{
///
/// The row Id.
diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs
index 85327de2e2..521e671be8 100644
--- a/src/Tgstation.Server.Host/Models/Job.cs
+++ b/src/Tgstation.Server.Host/Models/Job.cs
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Models
{
///
#pragma warning disable CA1724 // naming conflict with gitlab package
- public sealed class Job : Api.Models.Internal.Job, IApiTransformable
+ public sealed class Job : Api.Models.Internal.Job, ILegacyApiTransformable
#pragma warning restore CA1724
{
///
diff --git a/src/Tgstation.Server.Host/Models/OAuthConnection.cs b/src/Tgstation.Server.Host/Models/OAuthConnection.cs
index d622b358c8..64b1702d8c 100644
--- a/src/Tgstation.Server.Host/Models/OAuthConnection.cs
+++ b/src/Tgstation.Server.Host/Models/OAuthConnection.cs
@@ -1,7 +1,7 @@
namespace Tgstation.Server.Host.Models
{
///
- public sealed class OAuthConnection : Api.Models.OAuthConnection, IApiTransformable
+ public sealed class OAuthConnection : Api.Models.OAuthConnection, ILegacyApiTransformable
{
///
/// The row Id.
diff --git a/src/Tgstation.Server.Host/Models/RepositorySettings.cs b/src/Tgstation.Server.Host/Models/RepositorySettings.cs
index d804162718..9f36e2f123 100644
--- a/src/Tgstation.Server.Host/Models/RepositorySettings.cs
+++ b/src/Tgstation.Server.Host/Models/RepositorySettings.cs
@@ -5,7 +5,7 @@ using Tgstation.Server.Api.Models.Response;
namespace Tgstation.Server.Host.Models
{
///
- public sealed class RepositorySettings : Api.Models.RepositorySettings, IApiTransformable
+ public sealed class RepositorySettings : Api.Models.RepositorySettings, ILegacyApiTransformable
{
///
/// The row Id.
diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs
index eb99816ab6..d683e9c8b7 100644
--- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs
+++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs
@@ -6,7 +6,7 @@ using System.Linq;
namespace Tgstation.Server.Host.Models
{
///
- public sealed class RevisionInformation : Api.Models.Internal.RevisionInformation, IApiTransformable
+ public sealed class RevisionInformation : Api.Models.Internal.RevisionInformation, ILegacyApiTransformable
{
///
/// The row Id.
diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs
index 3641640378..1aaf31594a 100644
--- a/src/Tgstation.Server.Host/Models/TestMerge.cs
+++ b/src/Tgstation.Server.Host/Models/TestMerge.cs
@@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations;
namespace Tgstation.Server.Host.Models
{
///
- public sealed class TestMerge : Api.Models.Internal.TestMergeApiBase, IApiTransformable
+ public sealed class TestMerge : Api.Models.Internal.TestMergeApiBase, ILegacyApiTransformable
{
///
/// See .
diff --git a/src/Tgstation.Server.Host/Models/Transformers/TransformerBase{TModel,TApiModel}.cs b/src/Tgstation.Server.Host/Models/Transformers/TransformerBase{TModel,TApiModel}.cs
new file mode 100644
index 0000000000..e3d16441cc
--- /dev/null
+++ b/src/Tgstation.Server.Host/Models/Transformers/TransformerBase{TModel,TApiModel}.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Linq.Expressions;
+
+namespace Tgstation.Server.Host.Models.Transformers
+{
+ ///
+ abstract class TransformerBase : ITransformer
+ {
+ ///
+ /// cache for .
+ ///
+ static Func? compiledExpression;
+
+ ///
+ public Expression> Expression { get; }
+
+ ///
+ public Func CompiledExpression { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ protected TransformerBase(
+ Expression> expression)
+ {
+ compiledExpression ??= expression.Compile();
+ Expression = expression;
+ CompiledExpression = compiledExpression;
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Models/Transformers/UserGraphQLTransformer.cs b/src/Tgstation.Server.Host/Models/Transformers/UserGraphQLTransformer.cs
new file mode 100644
index 0000000000..9c1184ecce
--- /dev/null
+++ b/src/Tgstation.Server.Host/Models/Transformers/UserGraphQLTransformer.cs
@@ -0,0 +1,26 @@
+namespace Tgstation.Server.Host.Models.Transformers
+{
+ ///
+ /// for s.
+ ///
+ sealed class UserGraphQLTransformer : TransformerBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public UserGraphQLTransformer()
+ : base(model => new GraphQL.Types.User
+ {
+ CreatedAt = model.CreatedAt!.Value,
+ CanonicalName = model.CanonicalName!,
+ CreatedById = model.CreatedById,
+ Enabled = model.Enabled!.Value,
+ GroupId = model.GroupId,
+ Id = model.Id!.Value,
+ Name = model.Name!,
+ SystemIdentifier = model.SystemIdentifier,
+ })
+ {
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs
index d45d579d60..46c5530da1 100644
--- a/src/Tgstation.Server.Host/Models/User.cs
+++ b/src/Tgstation.Server.Host/Models/User.cs
@@ -5,11 +5,14 @@ using System.Linq;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
+using Tgstation.Server.Host.Models.Transformers;
namespace Tgstation.Server.Host.Models
{
///
- public sealed class User : Api.Models.Internal.UserModelBase, IApiTransformable, IApiTransformable
+ public sealed class User : Api.Models.Internal.UserModelBase,
+ ILegacyApiTransformable,
+ IApiTransformable
{
///
/// Username used when creating jobs automatically.
@@ -83,18 +86,6 @@ namespace Tgstation.Server.Host.Models
///
public UserResponse ToApi() => CreateUserResponse(true);
- ///
- GraphQL.Types.User IApiTransformable.ToApi()
- => new(
- Id!.Value,
- Name!,
- CanonicalName!,
- SystemIdentifier,
- CreatedAt!.Value,
- CreatedById,
- GroupId,
- Enabled!.Value);
-
///
/// Generate a from .
///
diff --git a/src/Tgstation.Server.Host/Models/UserGroup.cs b/src/Tgstation.Server.Host/Models/UserGroup.cs
index 4451c1a073..c8fa3efa79 100644
--- a/src/Tgstation.Server.Host/Models/UserGroup.cs
+++ b/src/Tgstation.Server.Host/Models/UserGroup.cs
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Models
///
/// Represents a group of s.
///
- public sealed class UserGroup : NamedEntity, IApiTransformable
+ public sealed class UserGroup : NamedEntity, ILegacyApiTransformable
{
///
/// The the has.
diff --git a/src/Tgstation.Server.Host/Properties/AssemblyInfo.cs b/src/Tgstation.Server.Host/Properties/AssemblyInfo.cs
index 47d191b80a..23e85d3ef1 100644
--- a/src/Tgstation.Server.Host/Properties/AssemblyInfo.cs
+++ b/src/Tgstation.Server.Host/Properties/AssemblyInfo.cs
@@ -1,6 +1,10 @@
using System.Runtime.CompilerServices;
+using GreenDonut;
+
[assembly: InternalsVisibleTo("Tgstation.Server.Host.Tests")]
[assembly: InternalsVisibleTo("Tgstation.Server.Host.Tests.Signals")]
[assembly: InternalsVisibleTo("Tgstation.Server.Tests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
+
+[assembly: DataLoaderDefaults(AccessModifier = DataLoaderAccessModifier.Internal)]
diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
index 3192e8faa9..72b62df1f3 100644
--- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
+++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
@@ -100,11 +100,15 @@
-
+
-
+
+
+
+
+
-
+