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);
+ }
+}