GitHub PullRequestReviewCommentEvent at 2014-12-05 18:25:29
Repository: grasshopper
Actor: nicolaasmatthijs
Payload: {"action":"created","comment":{"url":"https://api.github.com/repos/fronteerio/grasshopper/pulls/comments/21389435","id":21389435,"diff_hunk":"@@ -0,0 +1,557 @@\n+/**\n+ * Copyright (c) 2014 \"Fronteer LTD\"\n+ * Grasshopper Event Engine\n+ *\n+ * This program is free software: you can redistribute it and/or modify\n+ * it under the terms of the GNU Affero General Public License as\n+ * published by the Free Software Foundation, either version 3 of the\n+ * License, or (at your option) any later version.\n+ *\n+ * This program is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ * GNU Affero General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU Affero General Public License\n+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n+ */\n+\n+var _ = require('lodash');\n+\n+var GrasshopperUtil = require('gh-core/lib/util');\n+var GroupsAPI = require('gh-groups');\n+var GroupsDAO = require('gh-groups/lib/internal/dao');\n+var log = require('gh-core/lib/logger').logger('gh-orgunit');\n+var SeriesDAO = require('gh-series/lib/internal/dao');\n+var Validator = require('gh-core/lib/validator').Validator;\n+\n+var OrgUnitAuthz = require('./authz');\n+var OrgUnitDAO = require('./internal/dao');\n+\n+/**\n+ * Create an organisational unit\n+ *\n+ * @param  {Context}        ctx                 Standard context containing the current user and the current app\n+ * @param  {Number}         appId               The id of the application on which to create the organisational unit\n+ * @param  {String}         displayName         The name of the organisational unit\n+ * @param  {String}         type                The organisational unit type (e.g. `tripos`, `part`)\n+ * @param  {String}         description         The description of the organisational unit\n+ * @param  {Number}         groupId             The id of the group that can manage the organisational unit. Defaults to creating a new group with the current user as a member\n+ * @param  {Number}         parentId            The id of the parent organisational unit\n+ * @param  {Function}       callback            Standard callback function\n+ * @param  {Object}         callback.err        An error object, if any\n+ * @param  {OrgUnit}        callback.orgUnit    The created organisational unit\n+ */\n+var createOrgUnit = module.exports.createOrgUnit = function(ctx, appId, displayName, type, description, groupId, parentId, callback) {\n+    // Ensure that the app id is a valid number\n+    appId = GrasshopperUtil.getNumberParam(appId);\n+\n+    var validator = new Validator();\n+    validator.check(null, {'code': 401, 'msg': 'Only application administrators can create an organisational unit'}).isAdmin(ctx, appId);\n+    validator.check(appId, {'code': 400, 'msg': 'A valid application id must be provided'}).isInt();\n+    validator.check(displayName, {'code': 400, 'msg': 'A valid displayName must be provided'}).notEmpty();\n+    validator.check(displayName, {'code': 400, 'msg': 'A displayName can be at most 256 characters long'}).isShortString();\n+    validator.check(type, {'code': 400, 'msg': 'A valid type must be provided'}).notEmpty();\n+    validator.check(type, {'code': 400, 'msg': 'A type can be at most 16 characters long'}).len(1, 16);\n+    if (description) {\n+        validator.check(description, {'code': 400, 'msg': 'A description can be at most 256 characters long'}).isMediumString();\n+    }\n+    if (groupId) {\n+        validator.check(groupId, {'code': 400, 'msg': 'A valid group must be a positive integer'}).isInt();\n+    }\n+    if (parentId) {\n+        validator.check(parentId, {'code': 400, 'msg': 'A valid parent organisational unit id must be a positive integer'}).isInt();\n+    }\n+    if (validator.hasErrors()) {\n+        return callback(validator.getFirstError());\n+    }\n+    \n+    // Ensure that the group id (if any) is a valid number\n+    groupId = GrasshopperUtil.getNumberParam(groupId);\n+\n+    // Ensure that the parent id (if any) is a valid number\n+    parentId = GrasshopperUtil.getNumberParam(parentId);\n+\n+    // Ensure the current user can create an organisational unit\n+    OrgUnitAuthz.canCreateOrgUnit(ctx, appId, function(err, canCreate) {\n+        if (err) {\n+            return callback(err);\n+        } else if (!canCreate) {\n+            log().warn({\n+                'actor': ctx.user.id,\n+                'displayName': displayName,\n+                'parentId': parentId\n+            }, 'An unauthorized attempt at creating an organisational unit occurred');\n+            return callback({'code': 401, 'msg': 'You are not allowed to create an organisational unit'});\n+        }\n+\n+        // If a parent was specified, ensure it exists\n+        _getOptionalOrgUnit(ctx, parentId, function(err, parent) {\n+            if (err) {\n+                return callback(err);\n+\n+            // Ensure the parent is from the same application\n+            } else if (parent && parent.AppId !== appId) {\n+                return callback({'code': 400, 'msg': 'You cannot create an organisational unit under an organisational unit from another application'});\n+            }\n+\n+            // If a group was specified, ensure it exists. Create\n+            // a group if none was specified\n+            GroupsAPI.getOrCreateGroup(ctx, appId, groupId, function(err, group) {\n+                if (err) {\n+                    return callback(err);\n+                }\n+\n+                // Create the organisational unit\n+                OrgUnitDAO.createOrgUnit(appId, displayName, type, description, group.id, parentId, callback);\n+            });\n+        });\n+    });\n+};\n+\n+/**\n+ * Get an organisational unit\n+ *\n+ * @param  {Context}        ctx                 Standard context containing the current user and the current app\n+ * @param  {Number}         id                  The id of the organisational unit to retrieve\n+ * @param  {Boolean}        [includeSeries]     Whether to include the event series associated to the organisational units. Defaults to `false`\n+ * @param  {Function}       callback            Standard callback function\n+ * @param  {Object}         callback.err        An error object, if any\n+ * @param  {OrgUnit}        callback.orgUnit    The created organisational unit\n+ */\n+var getOrgUnit = module.exports.getOrgUnit = function(ctx, id, includeSeries, callback) {\n+    // Ensure that the id is a valid number\n+    id = GrasshopperUtil.getNumberParam(id);\n+\n+    // Ensure that the includeSeries flag is a boolean\n+    includeSeries = GrasshopperUtil.getBooleanParam(includeSeries, false);\n+\n+    var validator = new Validator();\n+    validator.check(id, {'code': 400, 'msg': 'A valid organisational unit id is a positive integer'}).isInt();\n+    if (validator.hasErrors()) {\n+        return callback(validator.getFirstError());\n+    }\n+\n+    OrgUnitDAO.getOrgUnit(id, includeSeries, callback);\n+};\n+\n+/**\n+ * Get the organisational units for an application\n+ *\n+ * @param  {Context}        ctx                 Standard context containing the current user and the current app\n+ * @param  {Number}         appId               The id of the app to get the organisational units for\n+ * @param  {Number}         [parentId]          The id of the parent to retrieve the organisational units for\n+ * @param  {String[]}       [types]             The organisational unit type[s] to filter the organisational unit by\n+ * @param  {Boolean}        [includeSeries]     Whether to include the event series associated to the organisational units. Defaults to `false`\n+ * @param  {Function}       callback            Standard callback function\n+ * @param  {Object}         callback.err        An error object, if any\n+ * @param  {OrgUnit}        callback.orgUnits   The matching organisational units\n+ */\n+var getOrgUnits = module.exports.getOrgUnits = function(ctx, appId, parentId, types, includeSeries, callback) {\n+    // Ensure that the app id (if any) is a valid number\n+    appId = GrasshopperUtil.getNumberParam(appId);\n+\n+    // Ensure that the parent id (if any) is a valid number\n+    parentId = GrasshopperUtil.getNumberParam(parentId);\n+\n+    var validator = new Validator();\n+    validator.check(appId, {'code': 400, 'msg': 'A valid application id must be provided'}).isInt();\n+    if (parentId) {\n+        validator.check(parentId, {'code': 400, 'msg': 'A valid organisational unit id is a positive integer'}).isInt();\n+    }\n+    if (types) {\n+        validator.check(null, {'code': 400, 'msg': 'Types must be specified as an array'}).isArray(types);\n+        _.each(types, function(type) {\n+            validator.check(type, {'code': 400, 'msg': 'A valid type must be between 1 and 16 characters long'}).len(1, 16);\n+        });\n+    }\n+    if (validator.hasErrors()) {\n+        return callback(validator.getFirstError());\n+    } else if (appId !== ctx.app.id && (!ctx.user || !ctx.user.isGlobalAdmin())) {\n+        return callback({'code': 401, 'msg': 'You cannot get the organisational units from another application'});\n+    }\n+\n+    // Get the parent organisational unit, if any\n+    _getOptionalOrgUnit(ctx, parentId, function(err, parent) {\n+        if (err) {\n+            return callback(err);\n+        } else if (parent && parent.AppId !== appId && !ctx.user.isGlobalAdmin()) {\n+            return callback({'code': 401, 'msg': 'You cannot get the organisational units under an organisational unit from another application'});\n+        }\n+\n+        // Get the organisational units in the application\n+        OrgUnitDAO.getOrgUnits(appId, parentId, types, includeSeries, function(err, orgUnits) {\n+            if (err) {\n+                return callback(err);\n+            }\n+\n+            // If the current user is anonymous or no organisational\n+            // units were found, we return them as is\n+            if (!ctx.user || _.isEmpty(orgUnits)) {\n+                // Add a `canManage` property to each group\n+                var augmentedOrgUnits = _.map(orgUnits, function(orgUnit) {\n+                    var augmentedOrgUnit = orgUnit.values;\n+                    augmentedOrgUnit.canManage = false;\n+                    return augmentedOrgUnit;\n+                });\n+                return callback(null, augmentedOrgUnits);\n+            }\n+\n+            // Otherwise we get the groups the current user is a member of so we can mark\n+            // those organisational units that the user can edit\n+            // TODO: Make this optional?","path":"node_modules/gh-orgunit/lib/api.js","position":202,"original_position":202,"commit_id":"47cd969c993e2c89db32c6edaffeeea8f1112982","original_commit_id":"47cd969c993e2c89db32c6edaffeeea8f1112982","user":{"login":"nicolaasmatthijs","id":109850,"avatar_url":"https://avatars.githubusercontent.com/u/109850?v=3","gravatar_id":"","url":"https://api.github.com/users/nicolaasmatthijs","html_url":"https://github.com/nicolaasmatthijs","followers_url":"https://api.github.com/users/nicolaasmatthijs/followers","following_url":"https://api.github.com/users/nicolaasmatthijs/following{/other_user}","gists_url":"https://api.github.com/users/nicolaasmatthijs/gists{/gist_id}","starred_url":"https://api.github.com/users/nicolaasmatthijs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nicolaasmatthijs/subscriptions","organizations_url":"https://api.github.com/users/nicolaasmatthijs/orgs","repos_url":"https://api.github.com/users/nicolaasmatthijs/repos","events_url":"https://api.github.com/users/nicolaasmatthijs/events{/privacy}","received_events_url":"https://api.github.com/users/nicolaasmatthijs/received_events","type":"User","site_admin":false},"body":"It would be a good idea to make this optional indeed","created_at":"2014-12-05T18:25:29Z","updated_at":"2014-12-05T18:25:29Z","html_url":"https://github.com/fronteerio/grasshopper/pull/15#discussion_r21389435","pull_request_url":"https://api.github.com/repos/fronteerio/grasshopper/pulls/15","_links":{"self":{"href":"https://api.github.com/repos/fronteerio/grasshopper/pulls/comments/21389435"},"html":{"href":"https://github.com/fronteerio/grasshopper/pull/15#discussion_r21389435"},"pull_request":{"href":"https://api.github.com/repos/fronteerio/grasshopper/pulls/15"}}},"pull_request":{"url":"https://api.github.com/repos/fronteerio/grasshopper/pulls/15","id":25359678,"html_url":"https://github.com/fronteerio/grasshopper/pull/15","diff_url":"https://github.com/fronteerio/grasshopper/pull/15.diff","patch_url":"https://github.com/fronteerio/grasshopper/pull/15.patch","issue_url":"https://api.github.com/repos/fronteerio/grasshopper/issues/15","number":15,"state":"open","locked":false,"title":"Organisational units, series, events and groups","user":{"login":"simong","id":92180,"avatar_url":"https://avatars.githubusercontent.com/u/92180?v=3","gravatar_id":"","url":"https://api.github.com/users/simong","html_url":"https://github.com/simong","followers_url":"https://api.github.com/users/simong/followers","following_url":"https://api.github.com/users/simong/following{/other_user}","gists_url":"https://api.github.com/users/simong/gists{/gist_id}","starred_url":"https://api.github.com/users/simong/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/simong/subscriptions","organizations_url":"https://api.github.com/users/simong/orgs","repos_url":"https://api.github.com/users/simong/repos","events_url":"https://api.github.com/users/simong/events{/privacy}","received_events_url":"https://api.github.com/users/simong/received_events","type":"User","site_admin":false},"body":"Pretty massive PR including:\r\n\r\nOrganisational units:\r\n * CRUD\r\n * Getting / Adding / Deleting series from an organisational unit\r\n * Getting the organisational unit tree\r\n\r\nSeries:\r\n * CRUD\r\n * Adding / Deleting events to/from series\r\n\r\nEvents:\r\n * CRUD\r\n\r\n\r\nIt's pretty rought, but I'll go do some self-review (which hopefully gets in before @nicolaasmatthijs gets on the plane)","created_at":"2014-12-02T17:39:05Z","updated_at":"2014-12-05T18:25:29Z","closed_at":null,"merged_at":null,"merge_commit_sha":"927636a46da38dc1df37152a15d7db332e668959","assignee":{"login":"nicolaasmatthijs","id":109850,"avatar_url":"https://avatars.githubusercontent.com/u/109850?v=3","gravatar_id":"","url":"https://api.github.com/users/nicolaasmatthijs","html_url":"https://github.com/nicolaasmatthijs","followers_url":"https://api.github.com/users/nicolaasmatthijs/followers","following_url":"https://api.github.com/users/nicolaasmatthijs/following{/other_user}","gists_url":"https://api.github.com/users/nicolaasmatthijs/gists{/gist_id}","starred_url":"https://api.github.com/users/nicolaasmatthijs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nicolaasmatthijs/subscriptions","organizations_url":"https://api.github.com/users/nicolaasmatthijs/orgs","repos_url":"https://api.github.com/users/nicolaasmatthijs/repos","events_url":"https://api.github.com/users/nicolaasmatthijs/events{/privacy}","received_events_url":"https://api.github.com/users/nicolaasmatthijs/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/fronteerio/grasshopper/pulls/15/commits","review_comments_url":"https://api.github.com/repos/fronteerio/grasshopper/pulls/15/comments","review_comment_url":"https://api.github.com/repos/fronteerio/grasshopper/pulls/comments/{number}","comments_url":"https://api.github.com/repos/fronteerio/grasshopper/issues/15/comments","statuses_url":"https://api.github.com/repos/fronteerio/grasshopper/statuses/47cd969c993e2c89db32c6edaffeeea8f1112982","head":{"label":"fronteerio:groups-events-series","ref":"groups-events-series","sha":"47cd969c993e2c89db32c6edaffeeea8f1112982","user":{"login":"fronteerio","id":9116505,"avatar_url":"https://avatars.githubusercontent.com/u/9116505?v=3","gravatar_id":"","url":"https://api.github.com/users/fronteerio","html_url":"https://github.com/fronteerio","followers_url":"https://api.github.com/users/fronteerio/followers","following_url":"https://api.github.com/users/fronteerio/following{/other_user}","gists_url":"https://api.github.com/users/fronteerio/gists{/gist_id}","starred_url":"https://api.github.com/users/fronteerio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fronteerio/subscriptions","organizations_url":"https://api.github.com/users/fronteerio/orgs","repos_url":"https://api.github.com/users/fronteerio/repos","events_url":"https://api.github.com/users/fronteerio/events{/privacy}","received_events_url":"https://api.github.com/users/fronteerio/received_events","type":"Organization","site_admin":false},"repo":{"id":24999124,"name":"grasshopper","full_name":"fronteerio/grasshopper","owner":{"login":"fronteerio","id":9116505,"avatar_url":"https://avatars.githubusercontent.com/u/9116505?v=3","gravatar_id":"","url":"https://api.github.com/users/fronteerio","html_url":"https://github.com/fronteerio","followers_url":"https://api.github.com/users/fronteerio/followers","following_url":"https://api.github.com/users/fronteerio/following{/other_user}","gists_url":"https://api.github.com/users/fronteerio/gists{/gist_id}","starred_url":"https://api.github.com/users/fronteerio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fronteerio/subscriptions","organizations_url":"https://api.github.com/users/fronteerio/orgs","repos_url":"https://api.github.com/users/fronteerio/repos","events_url":"https://api.github.com/users/fronteerio/events{/privacy}","received_events_url":"https://api.github.com/users/fronteerio/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/fronteerio/grasshopper","description":"Grasshopper Event Engine","fork":false,"url":"https://api.github.com/repos/fronteerio/grasshopper","forks_url":"https://api.github.com/repos/fronteerio/grasshopper/forks","keys_url":"https://api.github.com/repos/fronteerio/grasshopper/keys{/key_id}","collaborators_url":"https://api.github.com/repos/fronteerio/grasshopper/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/fronteerio/grasshopper/teams","hooks_url":"https://api.github.com/repos/fronteerio/grasshopper/hooks","issue_events_url":"https://api.github.com/repos/fronteerio/grasshopper/issues/events{/number}","events_url":"https://api.github.com/repos/fronteerio/grasshopper/events","assignees_url":"https://api.github.com/repos/fronteerio/grasshopper/assignees{/user}","branches_url":"https://api.github.com/repos/fronteerio/grasshopper/branches{/branch}","tags_url":"https://api.github.com/repos/fronteerio/grasshopper/tags","blobs_url":"https://api.github.com/repos/fronteerio/grasshopper/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/fronteerio/grasshopper/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/fronteerio/grasshopper/git/refs{/sha}","trees_url":"https://api.github.com/repos/fronteerio/grasshopper/git/trees{/sha}","statuses_url":"https://api.github.com/repos/fronteerio/grasshopper/statuses/{sha}","languages_url":"https://api.github.com/repos/fronteerio/grasshopper/languages","stargazers_url":"https://api.github.com/repos/fronteerio/grasshopper/stargazers","contributors_url":"https://api.github.com/repos/fronteerio/grasshopper/contributors","subscribers_url":"https://api.github.com/repos/fronteerio/grasshopper/subscribers","subscription_url":"https://api.github.com/repos/fronteerio/grasshopper/subscription","commits_url":"https://api.github.com/repos/fronteerio/grasshopper/commits{/sha}","git_commits_url":"https://api.github.com/repos/fronteerio/grasshopper/git/commits{/sha}","comments_url":"https://api.github.com/repos/fronteerio/grasshopper/comments{/number}","issue_comment_url":"https://api.github.com/repos/fronteerio/grasshopper/issues/comments/{number}","contents_url":"https://api.github.com/repos/fronteerio/grasshopper/contents/{+path}","compare_url":"https://api.github.com/repos/fronteerio/grasshopper/compare/{base}...{head}","merges_url":"https://api.github.com/repos/fronteerio/grasshopper/merges","archive_url":"https://api.github.com/repos/fronteerio/grasshopper/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/fronteerio/grasshopper/downloads","issues_url":"https://api.github.com/repos/fronteerio/grasshopper/issues{/number}","pulls_url":"https://api.github.com/repos/fronteerio/grasshopper/pulls{/number}","milestones_url":"https://api.github.com/repos/fronteerio/grasshopper/milestones{/number}","notifications_url":"https://api.github.com/repos/fronteerio/grasshopper/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/fronteerio/grasshopper/labels{/name}","releases_url":"https://api.github.com/repos/fronteerio/grasshopper/releases{/id}","created_at":"2014-10-09T16:51:39Z","updated_at":"2014-12-05T11:00:25Z","pushed_at":"2014-12-05T13:20:07Z","git_url":"git://github.com/fronteerio/grasshopper.git","ssh_url":"git@github.com:fronteerio/grasshopper.git","clone_url":"https://github.com/fronteerio/grasshopper.git","svn_url":"https://github.com/fronteerio/grasshopper","homepage":null,"size":1656,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":3,"mirror_url":null,"open_issues_count":4,"forks":3,"open_issues":4,"watchers":0,"default_branch":"master"}},"base":{"label":"fronteerio:master","ref":"master","sha":"249eef64e0e285a174f9a1168ec96ac9f083dc92","user":{"login":"fronteerio","id":9116505,"avatar_url":"https://avatars.githubusercontent.com/u/9116505?v=3","gravatar_id":"","url":"https://api.github.com/users/fronteerio","html_url":"https://github.com/fronteerio","followers_url":"https://api.github.com/users/fronteerio/followers","following_url":"https://api.github.com/users/fronteerio/following{/other_user}","gists_url":"https://api.github.com/users/fronteerio/gists{/gist_id}","starred_url":"https://api.github.com/users/fronteerio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fronteerio/subscriptions","organizations_url":"https://api.github.com/users/fronteerio/orgs","repos_url":"https://api.github.com/users/fronteerio/repos","events_url":"https://api.github.com/users/fronteerio/events{/privacy}","received_events_url":"https://api.github.com/users/fronteerio/received_events","type":"Organization","site_admin":false},"repo":{"id":24999124,"name":"grasshopper","full_name":"fronteerio/grasshopper","owner":{"login":"fronteerio","id":9116505,"avatar_url":"https://avatars.githubusercontent.com/u/9116505?v=3","gravatar_id":"","url":"https://api.github.com/users/fronteerio","html_url":"https://github.com/fronteerio","followers_url":"https://api.github.com/users/fronteerio/followers","following_url":"https://api.github.com/users/fronteerio/following{/other_user}","gists_url":"https://api.github.com/users/fronteerio/gists{/gist_id}","starred_url":"https://api.github.com/users/fronteerio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fronteerio/subscriptions","organizations_url":"https://api.github.com/users/fronteerio/orgs","repos_url":"https://api.github.com/users/fronteerio/repos","events_url":"https://api.github.com/users/fronteerio/events{/privacy}","received_events_url":"https://api.github.com/users/fronteerio/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/fronteerio/grasshopper","description":"Grasshopper Event Engine","fork":false,"url":"https://api.github.com/repos/fronteerio/grasshopper","forks_url":"https://api.github.com/repos/fronteerio/grasshopper/forks","keys_url":"https://api.github.com/repos/fronteerio/grasshopper/keys{/key_id}","collaborators_url":"https://api.github.com/repos/fronteerio/grasshopper/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/fronteerio/grasshopper/teams","hooks_url":"https://api.github.com/repos/fronteerio/grasshopper/hooks","issue_events_url":"https://api.github.com/repos/fronteerio/grasshopper/issues/events{/number}","events_url":"https://api.github.com/repos/fronteerio/grasshopper/events","assignees_url":"https://api.github.com/repos/fronteerio/grasshopper/assignees{/user}","branches_url":"https://api.github.com/repos/fronteerio/grasshopper/branches{/branch}","tags_url":"https://api.github.com/repos/fronteerio/grasshopper/tags","blobs_url":"https://api.github.com/repos/fronteerio/grasshopper/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/fronteerio/grasshopper/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/fronteerio/grasshopper/git/refs{/sha}","trees_url":"https://api.github.com/repos/fronteerio/grasshopper/git/trees{/sha}","statuses_url":"https://api.github.com/repos/fronteerio/grasshopper/statuses/{sha}","languages_url":"https://api.github.com/repos/fronteerio/grasshopper/languages","stargazers_url":"https://api.github.com/repos/fronteerio/grasshopper/stargazers","contributors_url":"https://api.github.com/repos/fronteerio/grasshopper/contributors","subscribers_url":"https://api.github.com/repos/fronteerio/grasshopper/subscribers","subscription_url":"https://api.github.com/repos/fronteerio/grasshopper/subscription","commits_url":"https://api.github.com/repos/fronteerio/grasshopper/commits{/sha}","git_commits_url":"https://api.github.com/repos/fronteerio/grasshopper/git/commits{/sha}","comments_url":"https://api.github.com/repos/fronteerio/grasshopper/comments{/number}","issue_comment_url":"https://api.github.com/repos/fronteerio/grasshopper/issues/comments/{number}","contents_url":"https://api.github.com/repos/fronteerio/grasshopper/contents/{+path}","compare_url":"https://api.github.com/repos/fronteerio/grasshopper/compare/{base}...{head}","merges_url":"https://api.github.com/repos/fronteerio/grasshopper/merges","archive_url":"https://api.github.com/repos/fronteerio/grasshopper/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/fronteerio/grasshopper/downloads","issues_url":"https://api.github.com/repos/fronteerio/grasshopper/issues{/number}","pulls_url":"https://api.github.com/repos/fronteerio/grasshopper/pulls{/number}","milestones_url":"https://api.github.com/repos/fronteerio/grasshopper/milestones{/number}","notifications_url":"https://api.github.com/repos/fronteerio/grasshopper/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/fronteerio/grasshopper/labels{/name}","releases_url":"https://api.github.com/repos/fronteerio/grasshopper/releases{/id}","created_at":"2014-10-09T16:51:39Z","updated_at":"2014-12-05T11:00:25Z","pushed_at":"2014-12-05T13:20:07Z","git_url":"git://github.com/fronteerio/grasshopper.git","ssh_url":"git@github.com:fronteerio/grasshopper.git","clone_url":"https://github.com/fronteerio/grasshopper.git","svn_url":"https://github.com/fronteerio/grasshopper","homepage":null,"size":1656,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":3,"mirror_url":null,"open_issues_count":4,"forks":3,"open_issues":4,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/fronteerio/grasshopper/pulls/15"},"html":{"href":"https://github.com/fronteerio/grasshopper/pull/15"},"issue":{"href":"https://api.github.com/repos/fronteerio/grasshopper/issues/15"},"comments":{"href":"https://api.github.com/repos/fronteerio/grasshopper/issues/15/comments"},"review_comments":{"href":"https://api.github.com/repos/fronteerio/grasshopper/pulls/15/comments"},"review_comment":{"href":"https://api.github.com/repos/fronteerio/grasshopper/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/fronteerio/grasshopper/pulls/15/commits"},"statuses":{"href":"https://api.github.com/repos/fronteerio/grasshopper/statuses/47cd969c993e2c89db32c6edaffeeea8f1112982"}}}}