diff --git a/src/Tgstation.Server.Api/ModelAttribute.cs b/src/Tgstation.Server.Api/ModelAttribute.cs index face9e2617..74f9f2f161 100644 --- a/src/Tgstation.Server.Api/ModelAttribute.cs +++ b/src/Tgstation.Server.Api/ModelAttribute.cs @@ -9,43 +9,40 @@ namespace Tgstation.Server.Api public sealed class ModelAttribute : Attribute { /// - /// Right required to read the field + /// Right required to read the model /// public object ReadRight { get; set; } /// - /// Right required to write the field with an Update call + /// Right required to update the model /// - public object WriteRight - { - get => writeRight; - set - { - if (DenyWrite) - throw new InvalidOperationException("Cannot set WriteRight on a PermissionsAttribute with DenyRight set"); - writeRight = value; - } - } + public object WriteRight { get; set; } /// - /// If the field cannot be written to + /// If the Create and Delete actions are available for this model /// - public bool DenyWrite - { - get => denyWrite; - set - { - if (WriteRight != null) - throw new InvalidOperationException("Cannot set DenyRight on a PermissionsAttribute with WriteRight set"); - denyWrite = value; - } - } + public bool CanCrud { get; set; } + + /// + /// If the List action is available for this model + /// + public bool CanList { get; set; } + + /// + /// If the actions require an + /// + public bool RequiresInstance { get; set; } /// /// The enum type that designates access to the model. Must be an with the /// public Type RightsEnum { get; } + /// + /// Construct a + /// + public ModelAttribute() => CanList = true; + /// /// Construct a /// @@ -60,14 +57,5 @@ namespace Tgstation.Server.Api throw new ArgumentException("rightsEnum must be an integer type!", nameof(rightsEnum)); RightsEnum = rightsEnum; } - - /// - /// Backing field for - /// - object writeRight; - /// - /// Backing field for - /// - bool denyWrite; } } diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs index 0600435cce..8ce05a3491 100644 --- a/src/Tgstation.Server.Api/Models/Byond.cs +++ b/src/Tgstation.Server.Api/Models/Byond.cs @@ -18,7 +18,7 @@ namespace Tgstation.Server.Api.Models /// /// The of the installation. Will be if the user does not have permission to view it or there is no BYOND version installed. Only considers the and numbers /// - [Permissions(ComplexWrite = true, ReadRight = ByondRights.ReadInstalled)] + [Permissions(ReadRight = ByondRights.ReadInstalled, WriteRight = ByondRights.ChangeVersion)] public Version Version { get; set; } /// diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs index e70e01e48c..55056d5c58 100644 --- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs +++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs @@ -5,9 +5,9 @@ using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// - /// Represents an instance of BYOND's DreamDaemon game server + /// Represents an instance of BYOND's DreamDaemon game server. Create action starts the server. Delete action shuts down the server /// - [Model(typeof(DreamDaemonRights))] + [Model(typeof(DreamDaemonRights), CanCrud = true)] public sealed class DreamDaemon { /// diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs index ccca180148..5fcdb8fb51 100644 --- a/src/Tgstation.Server.Api/Models/DreamMaker.cs +++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs @@ -4,14 +4,14 @@ using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// - /// Represents the state of the DreamMaker compiler + /// Represents the state of the DreamMaker compiler. Create action starts a new compile. Delete action cancels the current compile /// - [Model(typeof(DreamMakerRights), ReadRight = DreamMakerRights.Read)] + [Model(typeof(DreamMakerRights), ReadRight = DreamMakerRights.Read, CanCrud = true)] public sealed class DreamMaker - { - /// - /// When the compilation started - /// + { + /// + /// When the compilation started + /// [Permissions(DenyWrite = true)] public DateTimeOffset StartedAt { get; set; } /// diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index ca0e060ee4..e9ca8c42b7 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -5,7 +5,7 @@ namespace Tgstation.Server.Api.Models /// /// Metadata about a server instance /// - [Model(typeof(InstanceManagerRights))] + [Model(typeof(InstanceManagerRights), CanCrud = true, CanList = true)] public sealed class Instance { /// diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index 3a79a434b0..9ef0173331 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -19,14 +19,14 @@ namespace Tgstation.Server.Api.Models /// public ByondRights ByondRights { get; set; } - /// - /// The of the - /// - DreamDaemonRights DreamDaemonRights { get; set; } + /// + /// The of the + /// + public DreamDaemonRights DreamDaemonRights { get; set; } - /// - /// The of the - /// - RepositoryRights RepositoryRights { get; set; } + /// + /// The of the + /// + public RepositoryRights RepositoryRights { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Job.cs b/src/Tgstation.Server.Api/Models/Job.cs new file mode 100644 index 0000000000..2aa7ac6c55 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Job.cs @@ -0,0 +1,41 @@ +using System; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Represents a long running job on the server + /// + [Model] + public sealed class Job + { + /// + /// The ID + /// + public long Id { get; set; } + + /// + /// Human readable description of the + /// + public string Description { get; set; } + + /// + /// When the was started + /// + public DateTimeOffset StartedAt { get; set; } + + /// + /// When the stopped + /// + public DateTimeOffset StoppedAt { get; set; } + + /// + /// If the was cancelled + /// + public bool Cancelled { get; set; } + + /// + /// If the has incremental progress, this will range from 1 - 100. 0 otherwise + /// + public int Progress { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/Token.cs b/src/Tgstation.Server.Api/Models/Token.cs index 0a23af5734..7a8e573efa 100644 --- a/src/Tgstation.Server.Api/Models/Token.cs +++ b/src/Tgstation.Server.Api/Models/Token.cs @@ -1,12 +1,12 @@ -using System.Net; -using Tgstation.Server.Api.Rights; +using System; +using System.Net; namespace Tgstation.Server.Api.Models { /// - /// Represents an access token for the server + /// Represents an access token for the server. Read action generates a new one. Update action expires all for user /// - [Model(typeof(TokenRights), DenyWrite = true)] + [Model] public sealed class Token { /// @@ -24,6 +24,11 @@ namespace Tgstation.Server.Api.Models /// public IPAddress IssuedTo { get; set; } + /// + /// When the was originally issued + /// + public DateTimeOffset IssuedAt { get; set; } + /// /// The token . Not modifiable, only appears once /// diff --git a/src/Tgstation.Server.Api/PermissionsAttribute.cs b/src/Tgstation.Server.Api/PermissionsAttribute.cs index ee100a34e5..fd35005dcb 100644 --- a/src/Tgstation.Server.Api/PermissionsAttribute.cs +++ b/src/Tgstation.Server.Api/PermissionsAttribute.cs @@ -14,64 +14,18 @@ namespace Tgstation.Server.Api public object ReadRight { get; set; } /// - /// Right required to write the field with an Update call + /// Right required to update the field /// - public object WriteRight - { - get => writeRight; - set - { - if (DenyWrite) - throw new InvalidOperationException("Cannot set WriteRight on a PermissionsAttribute with DenyRight set"); - if (ComplexWrite) - throw new InvalidOperationException("Cannot set WriteRight on a PermissionsAttribute with ComplexWrite set"); - writeRight = value; - } - } + public object WriteRight { get; set; } /// /// If the field cannot be written to /// - public bool DenyWrite - { - get => denyWrite; - set - { - if (WriteRight != null) - throw new InvalidOperationException("Cannot set DenyRight on a PermissionsAttribute with WriteRight set"); - if (ComplexWrite) - throw new InvalidOperationException("Cannot set DenyRight on a PermissionsAttribute with ComplexWrite set"); - denyWrite = value; - } - } + public bool DenyWrite { get; set; } /// /// If the field has multiple write permissions /// - public bool ComplexWrite - { - get => complexWrite; - set - { - if (WriteRight != null) - throw new InvalidOperationException("Cannot set ComplexWrite on a PermissionsAttribute with WriteRight set"); - if (ComplexWrite) - throw new InvalidOperationException("Cannot set ComplexWrite on a PermissionsAttribute with DenyWrite set"); - complexWrite = value; - } - } - - /// - /// Backing field for - /// - object writeRight; - /// - /// Backing field for - /// - bool denyWrite; - /// - /// Backing field for - /// - bool complexWrite; + public bool ComplexWrite { get; set; } } } diff --git a/src/Tgstation.Server.Api/Rights/ByondRights.cs b/src/Tgstation.Server.Api/Rights/ByondRights.cs index 0b96b9783a..4919ef2800 100644 --- a/src/Tgstation.Server.Api/Rights/ByondRights.cs +++ b/src/Tgstation.Server.Api/Rights/ByondRights.cs @@ -21,24 +21,16 @@ namespace Tgstation.Server.Api.Rights /// ReadStaged = 2, /// - /// User may install any BYOND version if none is installed + /// User may change to any BYOND version /// - Install = 4, - /// - /// User may upgrade the installed BYOND version - /// - Upgrade = 8, - /// - /// User may downgrade the installed BYOND version - /// - Downgrade = 16, + ChangeVersion = 4, /// /// User may cancel a pending installation job /// - Cancel = 32, + Cancel = 8, /// /// User may read the of the installation job /// - ReadStatus = 64, + ReadStatus = 16, } } diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs index 2dae519ade..a17ab1c6f8 100644 --- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs +++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs @@ -15,50 +15,46 @@ namespace Tgstation.Server.Api.Rights /// /// User may create the if it does not exist /// - Create = 1, - /// - /// User may delete the - /// - Delete = 2, + SetOrigin = 2, /// /// User may directly set the sha the 's HEAD points to /// - SetSha = 8, + SetSha = 4, /// /// User may fetch and merge GitHub pull requests /// - MergePullRequest = 16, + MergePullRequest = 8, /// /// User may use the update feature /// - Update = 32, + UpdateBranch = 16, /// /// User may change and /// - ChangeCommitter = 64, + ChangeCommitter = 32, /// /// User may change /// - ChangeTestMergeCommits = 128, + ChangeTestMergeCommits = 64, /// /// User may change /// - ChangeAutoUpdate = 256, + ChangeAutoUpdate = 128, /// /// User may read and change and /// - ChangeCredentials = 512, + ChangeCredentials = 256, /// /// User may reset the 's HEAD to the origin reference of whatever branch it is tracking /// - Reset = 1024, + Reset = 512, /// /// User may read all fields in the with the exception of /// - Read = 2048, + Read = 1024, /// /// User may set to a backup tag /// - CheckoutBackup, + CheckoutBackup = 2048, } } diff --git a/src/Tgstation.Server.Api/Route.cs b/src/Tgstation.Server.Api/Route.cs new file mode 100644 index 0000000000..9a29119de5 --- /dev/null +++ b/src/Tgstation.Server.Api/Route.cs @@ -0,0 +1,20 @@ +using System.Net.Http; + +namespace Tgstation.Server.Api +{ + /// + /// Represents a route to a server action + /// + public sealed class Route + { + /// + /// The path to the action + /// + public string Path { get; set; } + + /// + /// The method of the action + /// + public HttpMethod Method { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/RouteHelper.cs b/src/Tgstation.Server.Api/RouteHelper.cs new file mode 100644 index 0000000000..de9c1f5cf1 --- /dev/null +++ b/src/Tgstation.Server.Api/RouteHelper.cs @@ -0,0 +1,98 @@ +using System; +using System.Linq; +using System.Net.Http; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Api +{ + /// + /// Gets routes for a given model + /// + public static class RouteHelper + { + /// + /// Read a + /// + /// The model type to read + /// The optional ID to pass in + /// A route to the read action + static Route Read(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; + } + + /// + /// Get the to the read action for a given + /// + /// The model to read + /// to read from if required + /// A to the read action + public static Route Read(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(instance?.Id); + } + + /// + /// Get the to the update action for a given + /// + /// The model to update + /// A to the update action + public static Route Update(Instance instance) where TModel : class + { + var result = Read(instance); + result.Method = HttpMethod.Post; + return result; + } + + /// + /// Get the to the list action for a given + /// + /// The model to list + /// A to the list action + public static Route List(Instance instance) where TModel : class + { + var result = Read(instance); + result.Path = String.Concat(result.Path, '/', nameof(List)); + return result; + } + + /// + /// Get the to the create action for a given + /// + /// The model to create + /// A to the create action + public static Route Create(Instance instance) where TModel : class + { + var result = Read(instance); + result.Method = HttpMethod.Put; + return result; + } + + /// + /// Get the to the delete action for a given + /// + /// The model to delete + /// A to the delete action + public static Route Delete(Instance instance) where TModel : class + { + var result = Read(instance); + result.Method = HttpMethod.Delete; + return result; + } + + /// + /// Get the to a given + /// + /// The to get + /// A to the get action + public static Route GetJob(Job job) => Read(job.Id); + } +}