From 2e985fde69003c655980382c6cf61a2f5e84c43e Mon Sep 17 00:00:00 2001
From: Justin Ehlert <justinehlert@flywheel.io>
Date: Tue, 12 Dec 2017 14:17:48 -0600
Subject: [PATCH] Add ability to include templates and document APIs

Added documentation for collections.
---
 raml/schemas/input/collection-update.json     |   5 +-
 raml/schemas/input/collection.json            |   5 +-
 raml/schemas/input/note.json                  |   5 +-
 swagger/Gruntfile.js                          |  15 +-
 swagger/index.yaml                            |   2 +
 swagger/package-lock.json                     |  28 ++-
 swagger/package.json                          |   2 +
 swagger/paths/collections.yaml                | 214 ++++++++++++++++++
 swagger/paths/groups.yaml                     | 139 ++----------
 swagger/paths/index.yaml                      |  44 ++++
 .../{ => support}/tasks/flatten-swagger.js    |   0
 .../tasks/resolve-schema-links.js             |   0
 swagger/support/tasks/resolve-templates.js    |  69 ++++++
 swagger/support/walk.js                       |  33 +++
 swagger/swagger-ui/index.html                 |   5 +
 swagger/templates/analyses-list.yaml          |  27 +++
 ...analysis-files-create-ticket-filename.yaml |  50 ++++
 swagger/templates/analysis-files.yaml         |  45 ++++
 swagger/templates/analysis-item.yaml          |  37 +++
 swagger/templates/analysis-notes-item.yaml    |  29 +++
 swagger/templates/analysis-notes.yaml         |  32 +++
 swagger/templates/file-item.yaml              |  54 +++++
 swagger/templates/file-list-upload.yaml       |  27 +++
 swagger/templates/notes-note.yaml             |  52 +++++
 swagger/templates/notes.yaml                  |  28 +++
 swagger/templates/packfile-end.yaml           |  27 +++
 swagger/templates/packfile-start.yaml         |  26 +++
 swagger/templates/packfile.yaml               |  32 +++
 swagger/templates/permissions-user.yaml       |  53 +++++
 swagger/templates/permissions.yaml            |  28 +++
 swagger/templates/tags-tag.yaml               |  54 +++++
 swagger/templates/tags.yaml                   |  29 +++
 32 files changed, 1071 insertions(+), 125 deletions(-)
 create mode 100644 swagger/paths/collections.yaml
 rename swagger/{ => support}/tasks/flatten-swagger.js (100%)
 rename swagger/{ => support}/tasks/resolve-schema-links.js (100%)
 create mode 100644 swagger/support/tasks/resolve-templates.js
 create mode 100644 swagger/support/walk.js
 create mode 100644 swagger/templates/analyses-list.yaml
 create mode 100644 swagger/templates/analysis-files-create-ticket-filename.yaml
 create mode 100644 swagger/templates/analysis-files.yaml
 create mode 100644 swagger/templates/analysis-item.yaml
 create mode 100644 swagger/templates/analysis-notes-item.yaml
 create mode 100644 swagger/templates/analysis-notes.yaml
 create mode 100644 swagger/templates/file-item.yaml
 create mode 100644 swagger/templates/file-list-upload.yaml
 create mode 100644 swagger/templates/notes-note.yaml
 create mode 100644 swagger/templates/notes.yaml
 create mode 100644 swagger/templates/packfile-end.yaml
 create mode 100644 swagger/templates/packfile-start.yaml
 create mode 100644 swagger/templates/packfile.yaml
 create mode 100644 swagger/templates/permissions-user.yaml
 create mode 100644 swagger/templates/permissions.yaml
 create mode 100644 swagger/templates/tags-tag.yaml
 create mode 100644 swagger/templates/tags.yaml

diff --git a/raml/schemas/input/collection-update.json b/raml/schemas/input/collection-update.json
index e6b8b322..0548ea23 100644
--- a/raml/schemas/input/collection-update.json
+++ b/raml/schemas/input/collection-update.json
@@ -2,5 +2,8 @@
     "$schema": "http://json-schema.org/draft-04/schema#",
     "title": "Collection",
     "type": "object",
-    "allOf": [{"$ref": "../definitions/collection.json#/definitions/collection-input-with-contents"}]
+    "allOf": [{"$ref": "../definitions/collection.json#/definitions/collection-input-with-contents"}],
+    "example": {
+    	"$ref": "../../examples/input/collection-update.json"
+    }
 }
diff --git a/raml/schemas/input/collection.json b/raml/schemas/input/collection.json
index 52e8d482..bd52b275 100644
--- a/raml/schemas/input/collection.json
+++ b/raml/schemas/input/collection.json
@@ -3,5 +3,8 @@
     "title": "Collection",
     "type": "object",
     "allOf": [{"$ref": "../definitions/collection.json#/definitions/collection-input"}],
-    "required": ["label"]
+    "required": ["label"],
+    "example": {
+    	"$ref": "../../examples/input/collection.json"
+    }
 }
diff --git a/raml/schemas/input/note.json b/raml/schemas/input/note.json
index d103d0b2..abca1841 100644
--- a/raml/schemas/input/note.json
+++ b/raml/schemas/input/note.json
@@ -2,5 +2,8 @@
   "$schema": "http://json-schema.org/draft-04/schema#",
   "type": "object",
   "allOf":[{"$ref":"../definitions/note.json#/definitions/note-input"}],
-  "required": ["text"]
+  "required": ["text"],
+  "example": {
+  	"$ref": "../../examples/input/note.json"
+  }
 }
diff --git a/swagger/Gruntfile.js b/swagger/Gruntfile.js
index b13ddfb6..714414c0 100644
--- a/swagger/Gruntfile.js
+++ b/swagger/Gruntfile.js
@@ -6,7 +6,7 @@ var SWAGGER_UI_LIVE_RELOAD_PORT = 19009;
 
 module.exports = function(grunt) {
 	require('load-grunt-tasks')(grunt);
-	grunt.task.loadTasks('tasks/');
+	grunt.task.loadTasks('support/tasks/');
 
 	grunt.initConfig({
 		pkg: grunt.file.readJSON('package.json'),
@@ -76,12 +76,22 @@ module.exports = function(grunt) {
 			}
 		},
 
+		/**
+		 * Resolve templates within swagger
+		 */
+		resolveTemplates: {
+			core: {
+				src: 'build/swagger-flat.json',
+				dest: 'build/swagger-int.json'
+			}
+		},
+
 		/**
 		 * Resolve schema links in the swagger documentation
 		 */
 		resolveSchemaLinks: {
 			core: {
-				src: 'build/swagger-flat.json',
+				src: 'build/swagger-int.json',
 				dest: 'build/swagger-ui.json'
 			}
 		},
@@ -126,6 +136,7 @@ module.exports = function(grunt) {
 	grunt.registerTask('build-schema', [
 		'copy:schema', 
 		'flattenSwagger',
+		'resolveTemplates',
 		'resolveSchemaLinks'
 	]);
 
diff --git a/swagger/index.yaml b/swagger/index.yaml
index bebc3d88..00c1f324 100644
--- a/swagger/index.yaml
+++ b/swagger/index.yaml
@@ -24,6 +24,8 @@ tags:
     description: Group operations
   - name: jobs
     description: Job operations
+  - name: collections
+    description: Collection operations
 
 paths:
   $ref: ./paths/index.yaml
diff --git a/swagger/package-lock.json b/swagger/package-lock.json
index 7789e350..7c305d42 100644
--- a/swagger/package-lock.json
+++ b/swagger/package-lock.json
@@ -682,6 +682,14 @@
         "gaze": "1.1.2",
         "lodash": "3.10.1",
         "tiny-lr": "0.2.1"
+      },
+      "dependencies": {
+        "lodash": {
+          "version": "3.10.1",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
+          "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
+          "dev": true
+        }
       }
     },
     "grunt-known-options": {
@@ -701,6 +709,14 @@
         "hooker": "0.2.3",
         "lodash": "3.10.1",
         "underscore.string": "3.2.3"
+      },
+      "dependencies": {
+        "lodash": {
+          "version": "3.10.1",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
+          "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
+          "dev": true
+        }
       }
     },
     "grunt-legacy-log-utils": {
@@ -936,9 +952,9 @@
       }
     },
     "lodash": {
-      "version": "3.10.1",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
-      "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
+      "version": "4.17.4",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
+      "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=",
       "dev": true
     },
     "loud-rejection": {
@@ -1054,6 +1070,12 @@
         "minimatch": "3.0.4"
       }
     },
+    "mustache": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.0.tgz",
+      "integrity": "sha1-QCj3d4sXcIpImTCm5SrDvKDaQdA=",
+      "dev": true
+    },
     "native-promise-only": {
       "version": "0.8.1",
       "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz",
diff --git a/swagger/package.json b/swagger/package.json
index e4081430..bb353464 100644
--- a/swagger/package.json
+++ b/swagger/package.json
@@ -17,6 +17,8 @@
     "js-yaml": "^3.10.0",
     "json-refs": "^3.0.2",
     "load-grunt-tasks": "^3.5.2",
+    "lodash": "^4.17.4",
+    "mustache": "^2.3.0",
     "swagger-ui-dist": "^3.6.0"
   }
 }
diff --git a/swagger/paths/collections.yaml b/swagger/paths/collections.yaml
new file mode 100644
index 00000000..e858f436
--- /dev/null
+++ b/swagger/paths/collections.yaml
@@ -0,0 +1,214 @@
+# Global template arguments for collection
+collection_tmpl_args:
+  resource: collection
+  tag: collections
+  parameter: CollectionId
+
+collections:
+  get:
+    summary: List all collections.
+    operationId: get_all_collections
+    tags: 
+    - collections
+    responses:
+      '200':
+        schema:
+          $ref: schemas/output/collection-list.json
+        examples:
+          response:
+            $ref: examples/output/collection-list.json
+  post:
+    summary: Create a collection
+    operationId: create_collection
+    tags: 
+    - collections
+    parameters:
+      - in: body
+        name: body
+        schema:
+          $ref: schemas/input/collection.json
+    responses:
+      '200':
+        schema:
+          $ref: schemas/output/collection-new.json
+        examples:
+          response:
+            $ref: examples/output/collection-new.json
+      '400':
+        $ref: '#/responses/400:invalid-body-json'
+
+collections-curators:
+  get:
+    summary: List all curators of collections
+    operationId: get_all_collections_curators
+    tags: 
+    - collections
+    responses:
+      '200':
+        schema:
+          $ref: schemas/output/collection-curators-list.json
+        examples:
+          response:
+            $ref: examples/output/collection-curators-list.json
+collections-collection:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: CollectionId
+  get:
+    summary: Retrieve a single collection
+    operationId: get_collection
+    tags: 
+    - collections
+    responses:
+      '200':
+        schema:
+          $ref: schemas/output/collection.json
+        examples:
+          response:
+            $ref: examples/output/collection.json
+  put:
+    summary: Update a collection and its contents
+    operationId: modify_collection
+    tags: 
+    - collections
+    parameters:
+      - in: body
+        name: body
+        schema:
+          $ref: schemas/input/collection-update.json
+    responses:
+      '200':
+        description: Collection updated
+      '400':
+        $ref: '#/responses/400:invalid-body-json'
+  delete:
+    summary: Delete a collection
+    operationId: delete_collection
+    tags: 
+    - collections
+    responses:
+      '200':
+        description: Collection was deleted
+
+collections-collection-sessions:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: CollectionId
+  get:
+    summary: List sessions in a collection
+    operationId: get_collection_sessions
+    tags: 
+    - collections
+    responses:
+      '200':
+        description: ''
+        schema:
+          $ref: schemas/output/session-list.json
+        examples:
+          response:
+            $ref: examples/output/session-list.json
+
+collections-collection-acquisitions:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: CollectionId
+  get:
+    summary: List acquisitions in a collection
+    operationId: get_collection_acquisitions
+    tags: 
+    - collections
+    responses:
+      '200':
+        description: ''
+        schema:
+          $ref: schemas/output/acquisition-list.json
+        examples:
+          response:
+            $ref: examples/output/acquisition-list.json
+
+# ===== Tags =====
+collections-collection-tags:
+  $template: templates/tags.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+collections-collection-tags-tag:
+  $template: templates/tags-tag.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+
+# ===== Packfile =====
+collections-collection-packfile-start:
+  $template: templates/packfile-start.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+collections-collection-packfile:
+  $template: templates/packfile.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+collections-collection-packfile-end:
+  $template: templates/packfile-end.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+
+# ===== Files =====
+collections-collection-files:
+  $template: templates/file-list-upload.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+collections-collection-files-file:
+  $template: templates/file-item.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+
+# ===== Permissions =====
+collections-collection-permissions:
+  $template: templates/permissions.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+collections-collection-permissions-user:
+  $template: templates/permissions-user.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+
+# ===== Notes =====
+collections-collection-notes:
+  $template: templates/notes.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+collections-collection-notes-note:
+  $template: templates/notes-note.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+
+# ===== Analyses =====
+collections-collection-analyses:
+  $template: templates/analyses-list.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+collections-collection-analyses-analysis:
+  $template: templates/analysis-item.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+collections-collection-analyses-analysis-files:
+  $template: templates/analysis-files.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+collections-collection-analyses-analysis-files-filename:
+  $template: templates/analysis-files-create-ticket-filename.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+collections-collection-analyses-analysis-notes:
+  $template: templates/analysis-notes.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+collections-collection-analyses-analysis-notes-note:
+  $template: templates/analysis-notes-item.yaml
+  arguments:
+    $ref: '#/collection_tmpl_args'
+    
\ No newline at end of file
diff --git a/swagger/paths/groups.yaml b/swagger/paths/groups.yaml
index c850fce7..fa5f734e 100644
--- a/swagger/paths/groups.yaml
+++ b/swagger/paths/groups.yaml
@@ -1,3 +1,8 @@
+group_tmpl_args:
+  resource: group
+  tag: groups
+  parameter: GroupId
+
 groups:
   get:
     summary: List all groups
@@ -69,127 +74,27 @@ groups-group:
     responses:
       '200':
         $ref: '#/responses/200:deleted-with-count'
+
+# ===== Permissions =====
 groups-group-permissions:
-  parameters:
-    - required: true
-      type: string
-      in: path
-      name: GroupId
-  post:
-    summary: Add a permission
-    operationId: add_group_permission
-    tags:
-    - groups
-    parameters:
-      - in: body
-        name: body
-        schema:
-          $ref: schemas/input/permission.json
-    responses:
-      '200':
-        $ref: "#/responses/200:modified-with-count"
-      '400':
-        $ref: '#/responses/400:invalid-body-json'
+  $template: templates/permissions.yaml
+  arguments:
+    $ref: '#/group_tmpl_args'
 groups-group-permissions-user:
-  parameters:
-    - required: true
-      description: User which is granted the permission
-      type: string
-      in: path
-      name: UserId
-    - required: true
-      type: string
-      in: path
-      name: GroupId
-  get:
-    summary: List a user's permissions for this group.
-    operationId: get_group_user_permission
-    tags:
-    - groups
-    responses:
-      '200':
-        description: ''
-        schema:
-          $ref: schemas/output/permission.json
-        examples:
-          response:
-            $ref: examples/output/permission.json
-  put:
-    summary: Update a user's permission for this group.
-    operationId: modify_group_user_permission
-    tags:
-    - groups
-    parameters:
-      - in: body
-        name: body
-        schema:
-          $ref: schemas/input/permission.json
-    responses:
-      '200':
-        $ref: "#/responses/200:modified-with-count"
-      '400':
-        $ref: '#/responses/400:invalid-body-json'
-  delete:
-    summary: Delete a permission
-    operationId: delete_group_user_permission
-    tags:
-    - groups
-    responses:
-      '200':
-        $ref: "#/responses/200:modified-with-count"
+  $template: templates/permissions-user.yaml
+  arguments:
+    $ref: '#/group_tmpl_args'
+
+# ===== Tags =====
 groups-group-tags:
-  parameters:
-    - required: true
-      type: string
-      in: path
-      name: GroupId
-  post:
-    summary: Add a tag to a group.
-    description: Progates changes to projects, sessions and acquisitions
-    operationId: add_group_tag
-    tags:
-    - groups
-    responses:
-      '200':
-        $ref: "#/responses/200:modified-with-count"
-      '400':
-        $ref: '#/responses/400:invalid-body-json'
-    parameters:
-      $ref: ../common/tags.yaml#/add-tag-parameters
+  $template: templates/tags.yaml
+  arguments:
+    $ref: '#/group_tmpl_args'
 groups-group-tags-tag:
-  parameters:
-    - required: true
-      description: The tag to interact with
-      type: string
-      in: path
-      name: TagValue
-    - required: true
-      type: string
-      in: path
-      name: GroupId
-  get:
-    summary: Get the value of a group tag, by name.
-    operationId: get_group_tag
-    tags:
-    - groups
-    responses:
-        $ref: '../common/tags.yaml#/get-tag-responses'
-  put:
-    summary: Rename a tag on a group.
-    operationId: rename_group_tag
-    tags:
-    - groups
-    parameters:
-      $ref: ../common/tags.yaml#/modify-tag-parameters
-    responses:
-      $ref: ../common/tags.yaml#/modify-tag-responses
-  delete:
-    summary: Delete a tag on a group
-    operationId: delete_group_tag
-    tags:
-    - groups
-    responses:
-      $ref: ../common/tags.yaml#/delete-tag-responses
+  $template: templates/tags-tag.yaml
+  arguments:
+    $ref: '#/group_tmpl_args'
+
 groups-group-projects:
   parameters:
     - required: true
diff --git a/swagger/paths/index.yaml b/swagger/paths/index.yaml
index 950bc2f4..4e0c5a37 100644
--- a/swagger/paths/index.yaml
+++ b/swagger/paths/index.yaml
@@ -86,5 +86,49 @@
 '/groups/{GroupId}/projects':
   $ref: groups.yaml#/groups-group-projects
 
+/collections:
+  $ref: collections.yaml#/collections
+/collections/curators:
+  $ref: collections.yaml#/collections-curators
+'/collections/{CollectionId}':
+  $ref: collections.yaml#/collections-collection
+'/collections/{CollectionId}/sessions':
+  $ref: collections.yaml#/collections-collection-sessions
+'/collections/{CollectionId}/acquisitions':
+  $ref: collections.yaml#/collections-collection-acquisitions
+'/collections/{CollectionId}/tags':
+  $ref: collections.yaml#/collections-collection-tags
+'/collections/{CollectionId}/tags/{TagValue}':
+  $ref: collections.yaml#/collections-collection-tags-tag
+'/collections/{CollectionId}/packfile-start':
+  $ref: collections.yaml#/collections-collection-packfile-start
+'/collections/{CollectionId}/packfile':
+  $ref: collections.yaml#/collections-collection-packfile
+'/collections/{CollectionId}/packfile-end':
+  $ref: collections.yaml#/collections-collection-packfile-end
+'/collections/{CollectionId}/files':
+  $ref: collections.yaml#/collections-collection-files
+'/collections/{CollectionId}/files/{FileName}':
+  $ref: collections.yaml#/collections-collection-files-file
+'/collections/{CollectionId}/permissions':
+  $ref: collections.yaml#/collections-collection-permissions
+'/collections/{CollectionId}/permissions/{UserId}':
+  $ref: collections.yaml#/collections-collection-permissions-user
+'/collections/{CollectionId}/notes':
+  $ref: collections.yaml#/collections-collection-notes
+'/collections/{CollectionId}/notes/{NoteId}':
+  $ref: collections.yaml#/collections-collection-notes-note
+'/collections/{CollectionId}/analyses':
+  $ref: collections.yaml#/collections-collection-analyses
+'/collections/{CollectionId}/analyses/{AnalysisId}':
+  $ref: collections.yaml#/collections-collection-analyses-analysis
+'/collections/{CollectionId}/analyses/{AnalysisId}/files':
+  $ref: collections.yaml#/collections-collection-analyses-analysis-files
+'/collections/{CollectionId}/analyses/{AnalysisId}/files/{Filename}':
+  $ref: collections.yaml#/collections-collection-analyses-analysis-files-filename
+'/collections/{CollectionId}/analyses/{AnalysisId}/notes':
+  $ref: collections.yaml#/collections-collection-analyses-analysis-notes
+'/collections/{CollectionId}/analyses/{AnalysisId}/notes/{NoteId}':
+  $ref: collections.yaml#/collections-collection-analyses-analysis-notes-note
 
 
diff --git a/swagger/tasks/flatten-swagger.js b/swagger/support/tasks/flatten-swagger.js
similarity index 100%
rename from swagger/tasks/flatten-swagger.js
rename to swagger/support/tasks/flatten-swagger.js
diff --git a/swagger/tasks/resolve-schema-links.js b/swagger/support/tasks/resolve-schema-links.js
similarity index 100%
rename from swagger/tasks/resolve-schema-links.js
rename to swagger/support/tasks/resolve-schema-links.js
diff --git a/swagger/support/tasks/resolve-templates.js b/swagger/support/tasks/resolve-templates.js
new file mode 100644
index 00000000..66f93a18
--- /dev/null
+++ b/swagger/support/tasks/resolve-templates.js
@@ -0,0 +1,69 @@
+'use strict';
+
+module.exports = function(grunt) {
+	var path = require('path');
+	var fs = require('fs');
+	var process = require('process');
+	var yaml = require('js-yaml');
+	var mustache = require('mustache');
+	var _ = require('lodash');
+	var walk = require('../walk');
+
+	function resolveTemplatesFunc(templates) {
+		var loaded_templates = {};
+		return function(obj) {
+			return walk(obj, function(obj) {
+				var tmplpath, tmpl;
+
+				if( obj.hasOwnProperty('$template') ) {
+					tmplpath = path.join(templates, obj['$template']);
+					if( !loaded_templates[tmplpath] ) {
+						if( !fs.existsSync(tmplpath) ) {
+							throw 'Template file does note exist:' + tmplpath;
+						}
+						loaded_templates[tmplpath] = yaml.safeLoad(fs.readFileSync(tmplpath).toString());
+					}
+
+					tmpl = _.cloneDeep(loaded_templates[tmplpath]['template']);
+					obj = walk(tmpl, resolveParamsFunc(obj['arguments']));
+				}
+
+				return obj;
+			});
+		};
+	}
+
+	function resolveParamsFunc(args) {
+		return function(obj) {
+			if( typeof obj === 'string' ) {
+				return mustache.render(obj, args||{});
+			}
+			return obj;
+		};
+	}
+
+	grunt.registerMultiTask('resolveTemplates', 'Resolve templates in swagger JSON file', function() {
+		var src = this.data.src;
+		var dest = this.data.dest;
+		var templates = this.data.templates||process.cwd();
+		var resolveTemplates = resolveTemplatesFunc(templates);
+
+		if(!fs.existsSync(src)) {
+			grunt.log.writeln('Could not find:', src);
+			return false;
+		}
+
+		var root = JSON.parse(fs.readFileSync(src).toString());
+		try {
+			root = resolveTemplates(root);
+		} catch( e ) {
+			grunt.log.writeln('Error resolving templates:', e);
+			return false;
+		}
+
+		var data = JSON.stringify(root, null, 2);
+		fs.writeFileSync(dest, data);
+	});
+};
+
+
diff --git a/swagger/support/walk.js b/swagger/support/walk.js
new file mode 100644
index 00000000..e6cea587
--- /dev/null
+++ b/swagger/support/walk.js
@@ -0,0 +1,33 @@
+'use strict';
+
+var _ = require('lodash');
+
+module.exports = function objWalk(obj, callback, path, state) {
+	var i, idx;
+	state = state || {};
+
+	if( path ) {
+		path = path.slice()
+	} else {
+		path = [];
+	}
+	idx = path.length;
+
+	obj = callback(obj, path, state);
+
+	if( _.isArray(obj) ) {
+		for( i = 0; i < obj.length; i++ ) {
+			path[idx] = '[' + i + ']';
+			obj[i] = objWalk(obj[i], callback, path, _.cloneDeep(state));
+		}
+	} else if( _.isObjectLike(obj) ) {
+		for( i in obj ) {
+			if( obj.hasOwnProperty(i) ) {
+				path[idx] = i;
+				obj[i] = objWalk(obj[i], callback, path, _.cloneDeep(state));
+			}
+		}
+	}
+
+	return obj;
+}
diff --git a/swagger/swagger-ui/index.html b/swagger/swagger-ui/index.html
index ddf4a7b2..fbd764b7 100644
--- a/swagger/swagger-ui/index.html
+++ b/swagger/swagger-ui/index.html
@@ -30,6 +30,11 @@
     .topbar .download-url-wrapper {
         display: none !important;
     }
+
+    /* Temporarily disable errors until I can fix $refs */
+    .swagger-ui .errors-wrapper {
+      display: none !important;
+    }
   </style>
 </head>
 
diff --git a/swagger/templates/analyses-list.yaml b/swagger/templates/analyses-list.yaml
new file mode 100644
index 00000000..c7d37e93
--- /dev/null
+++ b/swagger/templates/analyses-list.yaml
@@ -0,0 +1,27 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+  post:
+    summary: Create an analysis and upload files.
+    operationId: add_{{resource}}_analysis
+    tags:
+    - '{{tag}}'
+    consumes:
+      - multipart/form-data
+    parameters:
+      - in: formData
+        name: formData
+        type: string
+    responses:
+      '200':
+        description: ''
diff --git a/swagger/templates/analysis-files-create-ticket-filename.yaml b/swagger/templates/analysis-files-create-ticket-filename.yaml
new file mode 100644
index 00000000..b1ada829
--- /dev/null
+++ b/swagger/templates/analysis-files-create-ticket-filename.yaml
@@ -0,0 +1,50 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+    - required: true
+      type: string
+      in: path
+      name: AnalysisId
+    - required: true
+      type: string
+      in: path
+      description: regex to select files for download
+      name: Filename
+  get:
+    summary: Download anaylsis files with filter.
+    description: >
+      If "ticket" query param is included and not empty, download files.
+
+      If "ticket" query param is included and empty, create a ticket for matching
+      files in the anlaysis.
+
+      If no "ticket" query param is included, files will be downloaded directly.
+    operationId: download_{{resource}}_analysis_files_by_filename
+    tags:
+    - '{{tag}}'
+    produces:
+      - application/json
+      - application/octet-stream
+    parameters:
+      - description: ticket id of the files to download
+        type: string
+        in: query
+        name: ticket
+    responses:
+      '200':
+        description: ''
+        schema:
+          $ref: schemas/output/analysis-files-create-ticket.json
+        examples:
+          response:
+            $ref: examples/output/analysis-files-create-ticket.json
\ No newline at end of file
diff --git a/swagger/templates/analysis-files.yaml b/swagger/templates/analysis-files.yaml
new file mode 100644
index 00000000..bcdc793c
--- /dev/null
+++ b/swagger/templates/analysis-files.yaml
@@ -0,0 +1,45 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+    - required: true
+      type: string
+      in: path
+      name: AnalysisId
+  get:
+    summary: Download analysis files.
+    description: >
+      If "ticket" query param is included and not empty, download files.
+
+      If "ticket" query param is included and empty, create a ticket for all
+      files in the anlaysis
+
+      If no "ticket" query param is included, server error 500
+    operationId: download_{{resource}}_analysis_files
+    tags: 
+    - '{{tag}}'
+    produces:
+      - application/json
+      - application/octet-stream
+    parameters:
+      - description: ticket id of the files to download
+        type: string
+        in: query
+        name: ticket
+    responses:
+      '200':
+        description: ''
+        schema:
+          $ref: schemas/output/analysis-files-create-ticket.json
+        examples:
+          response:
+            $ref: examples/output/analysis-files-create-ticket.json
\ No newline at end of file
diff --git a/swagger/templates/analysis-item.yaml b/swagger/templates/analysis-item.yaml
new file mode 100644
index 00000000..d187a0e5
--- /dev/null
+++ b/swagger/templates/analysis-item.yaml
@@ -0,0 +1,37 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+    parameters:
+      - required: true
+        type: string
+        in: path
+        name: '{{parameter}}'
+      - required: true
+        type: string
+        in: path
+        name: AnalysisId
+    get:
+      summary: Get an analysis.
+      operationId: get_{{resource}}_analysis
+      tags:
+      - '{{tag}}'
+      responses:
+        '200':
+          schema:
+            $ref: schemas/output/analysis.json
+          examples:
+            response:
+              $ref: examples/output/analysis.json
+    delete:
+      summary: Delete an anaylsis
+      operationId: delete_{{resource}}_analysis
+      tags:
+      - '{{tag}}'
+      responses:
+        '200':
+          $ref: '#/responses/200:deleted-with-count'
diff --git a/swagger/templates/analysis-notes-item.yaml b/swagger/templates/analysis-notes-item.yaml
new file mode 100644
index 00000000..62e26f90
--- /dev/null
+++ b/swagger/templates/analysis-notes-item.yaml
@@ -0,0 +1,29 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+    - required: true
+      type: string
+      in: path
+      name: AnalysisId
+    - required: true
+      type: string
+      in: path
+      name: NoteId
+  delete:
+    summary: Remove a note from {{resource}} analysis.
+    operationId: delete_{{resource}}_analysis_note
+    tags: 
+    - '{{tag}}'
+    responses:
+      '200':
+        $ref: '#/responses/200:modified-with-count'
diff --git a/swagger/templates/analysis-notes.yaml b/swagger/templates/analysis-notes.yaml
new file mode 100644
index 00000000..bbb5867e
--- /dev/null
+++ b/swagger/templates/analysis-notes.yaml
@@ -0,0 +1,32 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+    - required: true
+      type: string
+      in: path
+      name: AnalysisId
+  post:
+    summary: Add a note to {{resource}} analysis.
+    operationId: add_{{resource}}_analysis_note
+    tags:
+    - '{{tag}}'
+    parameters:
+      - name: body
+        in: body
+        schema:
+          $ref: schemas/input/note.json
+    responses:
+      '200':
+        $ref: '#/responses/200:modified-with-count'
+      '400':
+        $ref: '#/responses/400:invalid-body-json'
diff --git a/swagger/templates/file-item.yaml b/swagger/templates/file-item.yaml
new file mode 100644
index 00000000..54873dfa
--- /dev/null
+++ b/swagger/templates/file-item.yaml
@@ -0,0 +1,54 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+    - required: true
+      type: string
+      in: path
+      name: FileName
+  get:
+    summary: Download a file.
+    description: |
+      If "ticket" is specified by empty, a download ticket will be created
+      If "ticket" is not specified, the file will be download
+      If "ticket" is specified and not empty, the file will be downloaded
+    operationId: download_{{resource}}_file
+    tags: 
+    - '{{tag}}'
+    produces:
+      - application/json
+      - application/octet-stream
+    parameters:
+      - in: formData
+        name: formData
+        type: string
+      - description: ticket id of the file to download
+        type: string
+        in: query
+        name: ticket
+    responses:
+      '200':
+        description: ''
+        schema:
+          $ref: schemas/output/file-download.json
+        examples:
+          response:
+            $ref: examples/output/file-download.json
+  post:
+    summary: Replace a file
+    operationId: replace_{{resource}}_file
+    tags: 
+    - '{{tag}}'
+    responses:
+      default:
+        description: ''
+
diff --git a/swagger/templates/file-list-upload.yaml b/swagger/templates/file-list-upload.yaml
new file mode 100644
index 00000000..8b1be9a0
--- /dev/null
+++ b/swagger/templates/file-list-upload.yaml
@@ -0,0 +1,27 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+  post:
+    summary: Upload a file to {{resource}}.
+    operationId: upload_{{resource}}_file
+    tags: 
+    - '{{tag}}'
+    consumes:
+      - multipart/form-data
+    parameters:
+      - in: formData
+        name: formData
+        type: string
+    responses:
+      '200':
+        description: ''
diff --git a/swagger/templates/notes-note.yaml b/swagger/templates/notes-note.yaml
new file mode 100644
index 00000000..a00f2bc2
--- /dev/null
+++ b/swagger/templates/notes-note.yaml
@@ -0,0 +1,52 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+    - required: true
+      type: string
+      in: path
+      name: NoteId
+  get:
+    summary: Get a note on {{resource}}.
+    operationId: get_{{resource}}_note
+    tags: 
+    - '{{tag}}'
+    responses:
+      '200':
+        schema:
+          $ref: schemas/output/note.json
+        examples:
+          response:
+            $ref: examples/output/note.json
+  put:
+    summary: Update a note on {{resource}}.
+    operationId: modify_{{resource}}_note
+    tags: 
+    - '{{tag}}'
+    parameters:
+      - in: body
+        name: body
+        schema:
+          $ref: schemas/input/note.json
+    responses:
+      '200':
+        $ref: '#/responses/200:modified-with-count'
+      '400':
+        $ref: '#/responses/400:invalid-body-json'
+  delete:
+    summary: Remove a note from {{resource}}
+    operationId: delete_{{resource}}_note
+    tags: 
+    - '{{tag}}'
+    responses:
+      '200':
+        $ref: '#/responses/200:modified-with-count'
diff --git a/swagger/templates/notes.yaml b/swagger/templates/notes.yaml
new file mode 100644
index 00000000..540b1255
--- /dev/null
+++ b/swagger/templates/notes.yaml
@@ -0,0 +1,28 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+  post:
+    summary: Add a note to {{resource}}.
+    operationId: add_{{resource}}_note
+    tags:
+    - '{{tag}}'
+    parameters:
+      - name: body
+        in: body
+        schema:
+          $ref: schemas/input/note.json
+    responses:
+      '200':
+        $ref: '#/responses/200:modified-with-count'
+      '400':
+        $ref: '#/responses/400:invalid-body-json'
diff --git a/swagger/templates/packfile-end.yaml b/swagger/templates/packfile-end.yaml
new file mode 100644
index 00000000..fedd50cd
--- /dev/null
+++ b/swagger/templates/packfile-end.yaml
@@ -0,0 +1,27 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+  post:
+    summary: End a packfile upload
+    operationId: end_{{resource}}_packfile_upload
+    tags: 
+    - '{{tag}}'
+    produces:
+      - text/event-stream
+    parameters:
+      - in: formData
+        name: formData
+        type: string
+    responses:
+      '200':
+        description: ''
diff --git a/swagger/templates/packfile-start.yaml b/swagger/templates/packfile-start.yaml
new file mode 100644
index 00000000..25da57db
--- /dev/null
+++ b/swagger/templates/packfile-start.yaml
@@ -0,0 +1,26 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+  post:
+    summary: Start a packfile upload to {{resource}}
+    operationId: start_{{resource}}_packfile_upload
+    tags: 
+    - '{{tag}}'
+    responses:
+      '200':
+        description: ''
+        schema:
+          $ref: schemas/output/packfile-start.json
+        examples:
+          response:
+            $ref: examples/output/packfile-start.json
diff --git a/swagger/templates/packfile.yaml b/swagger/templates/packfile.yaml
new file mode 100644
index 00000000..bd2851f3
--- /dev/null
+++ b/swagger/templates/packfile.yaml
@@ -0,0 +1,32 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+  post:
+    summary: Add files to an in-progress packfile
+    operationId: '{{resource}}_packfile_upload'
+    tags: 
+    - '{{tag}}'
+    consumes:
+      - multipart/form-data
+    parameters:
+      - in: formData
+        name: formData
+        type: string
+    responses:
+      '200':
+        description: ''
+        schema:
+          $ref: schemas/output/file-list.json
+        examples:
+          response:
+            $ref: examples/file_info_list.json
diff --git a/swagger/templates/permissions-user.yaml b/swagger/templates/permissions-user.yaml
new file mode 100644
index 00000000..0a0875c4
--- /dev/null
+++ b/swagger/templates/permissions-user.yaml
@@ -0,0 +1,53 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+    - required: true
+      type: string
+      in: path
+      name: UserId
+  get:
+    summary: List a user's permissions for this {{resource}}.
+    operationId: get_{{resource}}_user_permission
+    tags:
+    - '{{tag}}'
+    responses:
+      '200':
+        description: ''
+        schema:
+          $ref: schemas/output/permission.json
+        examples:
+          response:
+            $ref: examples/output/permission.json
+  put:
+    summary: Update a user's permission for this {{resource}}.
+    operationId: modify_{{resource}}_user_permission
+    tags:
+    - '{{tag}}'
+    parameters:
+      - in: body
+        name: body
+        schema:
+          $ref: schemas/input/permission.json
+    responses:
+      '200':
+        $ref: "#/responses/200:modified-with-count"
+      '400':
+        $ref: '#/responses/400:invalid-body-json'
+  delete:
+    summary: Delete a permission
+    operationId: delete_{{resource}}_user_permission
+    tags:
+    - '{{tag}}'
+    responses:
+      '200':
+        $ref: "#/responses/200:modified-with-count"
diff --git a/swagger/templates/permissions.yaml b/swagger/templates/permissions.yaml
new file mode 100644
index 00000000..995d8746
--- /dev/null
+++ b/swagger/templates/permissions.yaml
@@ -0,0 +1,28 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+  post:
+    summary: Add a permission
+    operationId: add_{{resource}}_permission
+    tags:
+    - '{{tag}}'
+    parameters:
+      - in: body
+        name: body
+        schema:
+          $ref: schemas/input/permission.json
+    responses:
+      '200':
+        $ref: "#/responses/200:modified-with-count"
+      '400':
+        $ref: '#/responses/400:invalid-body-json'
diff --git a/swagger/templates/tags-tag.yaml b/swagger/templates/tags-tag.yaml
new file mode 100644
index 00000000..c868f4b5
--- /dev/null
+++ b/swagger/templates/tags-tag.yaml
@@ -0,0 +1,54 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+    parameters:
+      - required: true
+        type: string
+        in: path
+        name: '{{parameter}}'
+      - required: true
+        description: The tag to interact with
+        type: string
+        in: path
+        name: TagValue
+    get:
+      summary: Get the value of a tag, by name.
+      operationId: get_{{resource}}_tag
+      tags:
+      - '{{tag}}'
+      responses:
+        '200':
+          description: Returns a single tag by name
+          schema:
+            $ref: schemas/output/tag.json
+          examples:
+            response:
+              $ref: examples/output/tag.json
+    put:
+      summary: Rename a tag.
+      operationId: rename_{{resource}}_tag
+      tags:
+      - '{{tag}}'
+      parameters:
+        - name: body
+          in: body
+          schema:
+            $ref: schemas/input/tag.json
+      responses:
+        '200':
+          $ref: '#/responses/200:modified-with-count'
+        '400':
+          $ref: '#/responses/400:invalid-body-json'
+    delete:
+      summary: Delete a tag
+      operationId: delete_{{resource}}_tag
+      tags:
+      - '{{tag}}'
+      responses:
+        '200':
+          $ref: '#/responses/200:modified-with-count'
diff --git a/swagger/templates/tags.yaml b/swagger/templates/tags.yaml
new file mode 100644
index 00000000..b1a96893
--- /dev/null
+++ b/swagger/templates/tags.yaml
@@ -0,0 +1,29 @@
+parameters:
+  - name: resource
+    type: string
+  - name: parameter
+    type: string
+  - name: tag
+    type: string
+template:
+  parameters:
+    - required: true
+      type: string
+      in: path
+      name: '{{parameter}}'
+  post:
+    summary: Add a tag to {{resource}}.
+    description: Progates changes to projects, sessions and acquisitions
+    operationId: add_{{resource}}_tag
+    tags:
+    - '{{tag}}'
+    parameters:
+      - name: body
+        in: body
+        schema:
+          $ref: schemas/input/tag.json
+    responses:
+      '200':
+        $ref: '#/responses/200:modified-with-count'
+      '400':
+        $ref: '#/responses/400:invalid-body-json'
-- 
GitLab