diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj
index 3d2f700d3..5d3e88e44 100644
--- a/NzbDrone.Api/NzbDrone.Api.csproj
+++ b/NzbDrone.Api/NzbDrone.Api.csproj
@@ -90,6 +90,8 @@
+
+
diff --git a/NzbDrone.Api/QualityType/QualitySizeResource.cs b/NzbDrone.Api/QualityType/QualitySizeResource.cs
index 08e1a6653..f1dd33c5b 100644
--- a/NzbDrone.Api/QualityType/QualitySizeResource.cs
+++ b/NzbDrone.Api/QualityType/QualitySizeResource.cs
@@ -1,11 +1,10 @@
using System;
-using System.Linq;
+using NzbDrone.Api.REST;
namespace NzbDrone.Api.QualityType
{
- public class QualitySizeResource
+ public class QualitySizeResource : RestResource
{
- public Int32 Id { get; set; }
public String Name { get; set; }
public Int32 MinSize { get; set; }
public Int32 MaxSize { get; set; }
diff --git a/NzbDrone.Api/REST/RestModule.cs b/NzbDrone.Api/REST/RestModule.cs
new file mode 100644
index 000000000..a1b4f0ee0
--- /dev/null
+++ b/NzbDrone.Api/REST/RestModule.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using Nancy;
+using NzbDrone.Api.Extensions;
+
+namespace NzbDrone.Api.REST
+{
+ public abstract class RestModule : NancyModule
+ where TResource : RestResource, new()
+ {
+ private const string ROOT_ROUTE = "/";
+ private const string ID_ROUTE = "/{id}";
+
+ protected RestModule()
+ : this(new TResource().ResourceName)
+ {
+
+ }
+
+ protected RestModule(string modulePath)
+ : base(modulePath)
+ {
+ Get[ROOT_ROUTE] = options =>
+ {
+ EnsureImplementation(GetResourceAll);
+ var resource = GetResourceAll();
+ return resource.AsResponse();
+ };
+
+ Get[ID_ROUTE] = options =>
+ {
+ EnsureImplementation(GetResourceById);
+ var resource = GetResourceById(options.Id);
+ return resource.AsResponse();
+ };
+
+ Post[ROOT_ROUTE] = options =>
+ {
+ EnsureImplementation(CreateResource);
+ var resource = CreateResource(ReadFromRequest());
+ return resource.AsResponse(HttpStatusCode.Created);
+ };
+
+ Put[ROOT_ROUTE] = options =>
+ {
+ EnsureImplementation(UpdateResource);
+ var resource = UpdateResource(ReadFromRequest());
+ return resource.AsResponse(HttpStatusCode.Accepted);
+ };
+
+ Delete[ID_ROUTE] = options =>
+ {
+ EnsureImplementation(DeleteResource);
+ DeleteResource(options.Id);
+ return new Response { StatusCode = HttpStatusCode.OK };
+ };
+ }
+
+ protected Action DeleteResource { get; set; }
+ protected Func GetResourceById { get; set; }
+ protected Func> GetResourceAll { get; set; }
+ protected Func CreateResource { get; set; }
+ protected Func UpdateResource { get; set; }
+
+ private void EnsureImplementation(Delegate implementation)
+ {
+ if (implementation == null)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private TResource ReadFromRequest()
+ {
+ var resource = Request.Body.FromJson();
+
+ if (Request.Method.Equals("POST", StringComparison.InvariantCultureIgnoreCase))
+ {
+ resource.ValidateForPost();
+ }
+ else if (Request.Method.Equals("PUT", StringComparison.InvariantCultureIgnoreCase))
+ {
+ resource.ValidateForPut();
+ }
+
+ return resource;
+ }
+ }
+}
\ No newline at end of file
diff --git a/NzbDrone.Api/REST/RestResource.cs b/NzbDrone.Api/REST/RestResource.cs
new file mode 100644
index 000000000..5c93cea88
--- /dev/null
+++ b/NzbDrone.Api/REST/RestResource.cs
@@ -0,0 +1,42 @@
+using FluentValidation;
+
+namespace NzbDrone.Api.REST
+{
+ public abstract class RestResource
+ where T : RestResource, new()
+ {
+
+ public int Id { get; set; }
+
+ public virtual string ResourceName
+ {
+ get
+ {
+ return GetType().Name.ToLower();
+ }
+ }
+
+ protected AbstractValidator PostValidator { get; private set; }
+ protected AbstractValidator PutValidator { get; private set; }
+
+ public RestResource()
+ {
+ PostValidator = new InlineValidator();
+ PutValidator = new InlineValidator();
+
+ PostValidator.RuleFor(m => m.Id).Equal(0);
+ PutValidator.RuleFor(m => m.Id).GreaterThan(0);
+ }
+
+
+ public void ValidateForPost()
+ {
+ PostValidator.ValidateAndThrow((T)this);
+ }
+
+ public void ValidateForPut()
+ {
+ PutValidator.ValidateAndThrow((T)this);
+ }
+ }
+}
\ No newline at end of file