├── .envsample ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── captainhookbot.png └── haggis-pirate.png ├── index.js ├── knexfile.js ├── migrations ├── 1_create_users.js └── 2_create_subscriptions.js ├── package.json ├── seeds └── 0_users.js ├── server.js └── src ├── index.js ├── lib ├── api.js ├── base_model.js ├── base_routes.js ├── database.js ├── hook_receiver.js ├── logger.js └── slack_client.js ├── ping.js ├── resources ├── subscriptions │ ├── controller.js │ ├── helpers.js │ ├── messenger.js │ ├── model.js │ ├── routes.js │ ├── routes │ │ └── slack.js │ └── schema.js └── users │ ├── controller.js │ ├── model.js │ ├── routes.js │ └── schema.js └── routes.js /.envsample: -------------------------------------------------------------------------------- 1 | SLACK_API_TOKEN= 2 | SLACK_CHANNEL= 3 | SHARED_SECRET= 4 | DATABASE_URL= 5 | NPM_AUTH_TOKEN= 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, npm 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # captain-hook 2 | > a slack bot to subscribe to npm webhook notifications 3 | 4 | ![captain hook](assets/haggis-pirate.png) 5 | 6 | ## usage 7 | 8 | ``` 9 | /captain-hook subscribe 10 | ``` 11 | 12 | ## local dev 13 | 14 | `captain-hook` is a NodeJS application. if you don't have [NodeJS] 15 | you'll need to [download it]. 16 | 17 | `captain-hook` uses a Postgres database. 18 | 19 | ### up and running 20 | 21 | 1. Fork and clone this repo 22 | 2. `cd captain-hook` 23 | 3. `npm install` 24 | 4. Copy `.envsample` to `.env` and fill it out 25 | 26 | | variable | description | 27 | |------------------ |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 28 | | SLACK_API_TOKEN | Bot API token. You can find this on the Slack Developers site by navigating: Browse Apps > Custom Integrations > Bots Configurations on {{team_name}} > Edit configuration | 29 | | SLACK_CHANNEL_ID | Name of the channel, i.e. #testing. Can also be ID number retrieved from the Slack API. | 30 | | SHARED_SECRET | Any string. | 31 | | DATABASE_URL | URL of your Postgres database. defaults to `localhost:5432/dev` | 32 | 33 | 5. `npm db:setup`, creates a Postgres db, then runs [Knex] migrations and seeds 34 | 6. `npm run dev`, runs `npm start` and pipes output to nice logging 35 | You now have a service running on [`localhost:6666`]. You'll probably want to expose 36 | that to the internet for local development. [`ngrok`] is a great, free option. 37 | 38 | ## web interface 39 | 40 | the `captain hook` web interface is an express app. it servers basic CRUD functionality for the two resources: `users` and `subscriptions`. 41 | 42 | to start it, run `npm run start:web` and then visit [`http://localhost:8080`]. 43 | 44 | [NodeJS]: https://nodejs.org 45 | [download it]: https://nodejs.org 46 | [`localhost:6666`]: http://localhost:6666 47 | [`ngrok`]: https://ngrok.com/ 48 | [Knex]: http://knexjs.org/ 49 | [`http://localhost:8080`]: http://localhost:8080 50 | -------------------------------------------------------------------------------- /assets/captainhookbot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npm/captain-hook/90cdef34cbc13474ab09315a9a82084c326bef58/assets/captainhookbot.png -------------------------------------------------------------------------------- /assets/haggis-pirate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npm/captain-hook/90cdef34cbc13474ab09315a9a82084c326bef58/assets/haggis-pirate.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // load the env file 2 | require('dotenv').load(); 3 | var port = process.env.PORT || '6666'; 4 | 5 | // how we log 6 | const logger = require("./src/lib/logger").logger; 7 | const logHandler = require("./src/lib/logger").logHandler; 8 | 9 | // how we receive npm hooks 10 | const server = require("./src/lib/hook_receiver"); 11 | 12 | // how we post to slack 13 | const slack = require('./src/lib/slack_client').client; 14 | const channelID = require('./src/lib/slack_client').channelID; 15 | 16 | // routes live in a separate place 17 | const routes = require('./src/routes'); 18 | 19 | server.get('/ping', routes.ping); 20 | server.post('/slack', routes.subscriptions.slack); 21 | server.on('after', logHandler); 22 | 23 | // All hook events, with special handling for some. 24 | server.on('hook', function onIncomingHook(hook) { 25 | var pkg = hook.name.replace('/', '%2F'); 26 | var type = hook.type; 27 | var change = hook.event.replace(type + ':', ''); 28 | 29 | var message; 30 | console.log(hook.event); 31 | var user = hook.change ? hook.change.user : ''; 32 | 33 | switch (hook.event) { 34 | case 'package:star': 35 | message = `★ \ starred :package: \`; 36 | break; 37 | 38 | case 'package:unstar': 39 | message = `✩ \ unstarred :package: \`; 40 | break; 41 | 42 | case 'package:publish': 43 | message = `:package: \@${hook.change.version} published!`; 44 | break; 45 | 46 | case 'package:unpublish': 47 | message = `:package: \@${hook.change.version} unpublished`; 48 | break; 49 | 50 | case 'package:dist-tag': 51 | message = `:package: \@${hook.change.version} new dist-tag: \`${hook.change['dist-tag']}\``; 52 | break; 53 | 54 | case 'package:dist-tag-rm': 55 | message = `:package: \@${hook.change.version} dist-tag removed: \`${hook.change['dist-tag']}\``; 56 | break; 57 | 58 | case 'package:owner': 59 | message = `:package: \ owner added: \`${hook.change.user}\``; 60 | break; 61 | 62 | case 'package:owner-rm': 63 | message = `:package: \ owner removed: \`${hook.change.user}\``; 64 | break; 65 | 66 | default: 67 | message = [ 68 | `:package: \`, 69 | '*event*: ' + change, 70 | '*type*: ' + type, 71 | ].join('\n'); 72 | } 73 | 74 | var opts = { 75 | as_user: true, 76 | username: "captainhook" 77 | }; 78 | slack.chat.postMessage(channelID, message, opts); 79 | }); 80 | 81 | server.on('hook:error', function(message) { 82 | var opts = { 83 | as_user: true, 84 | username: "captainhook" 85 | } 86 | slack.chat.postMessage(channelID, '*error handling web hook:* ' + message); 87 | }); 88 | 89 | 90 | 91 | // start the server 92 | server.listen(port, function() { 93 | logger.info('listening on ' + port); 94 | var opts = { 95 | as_user: true, 96 | username: "captainhook" 97 | } 98 | slack.chat.postMessage(channelID, "arrrr captain hook reporting for duty", opts); 99 | }); 100 | -------------------------------------------------------------------------------- /knexfile.js: -------------------------------------------------------------------------------- 1 | require('dotenv').load(); 2 | 3 | module.exports = { 4 | development: { 5 | client: 'pg', 6 | connection: process.env.DATABASE_URL || 'postgresql://localhost:5432/dev' 7 | }, 8 | production: { 9 | client: 'pg', 10 | connection: process.env.DATABASE_URL || 'postgresql://localhost:5432/dev', 11 | connection: { 12 | user: 'root', 13 | password: process.env.DATABASE_PASSWORD, 14 | database: 'dev' 15 | } 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /migrations/1_create_users.js: -------------------------------------------------------------------------------- 1 | exports.up = function (knex) { 2 | return knex.schema 3 | .createTable('users', function (t) { 4 | t.increments('id'); 5 | t.string('slack-user-id').notNullable(); 6 | t.string('slack-team-id').notNullable(); 7 | t.string('npm-token-hashed'); 8 | }); 9 | }; 10 | 11 | exports.down = function (knex) { 12 | return knex.schema 13 | .dropTable('users'); 14 | }; 15 | -------------------------------------------------------------------------------- /migrations/2_create_subscriptions.js: -------------------------------------------------------------------------------- 1 | exports.up = function (knex) { 2 | return knex.schema 3 | .createTable('subscriptions', function(t) { 4 | t.increments('id'); 5 | t.integer('user_id').notNullable().references('id').inTable('users'); 6 | t.string('hook_id').notNullable(); 7 | t.string('type').notNullable(); 8 | t.string('name').notNullable(); 9 | t.string('event'); 10 | t.dateTime('date_created'); 11 | }); 12 | }; 13 | 14 | exports.down = function (knex) { 15 | return knex.schmea 16 | .dropTable('subscriptions'); 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slack-hook-subscribe", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "dev": "NODE_ENV=development npm start | bistre", 7 | "start": "node index", 8 | "db:setup": "createdb dev && knex migrate:latest && knex seed:run", 9 | "db:reset": "dropdb dev && npm run db:setup", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "npm-hook-receiver": "0.0.5", 17 | "@slack/client": "^3.1.0", 18 | "bistre": "^1.0.1", 19 | "body-parser": "^1.15.1", 20 | "bole": "^2.0.0", 21 | "bookshelf": "^0.9.5", 22 | "common-log-string": "^0.2.1", 23 | "endpoints": "^0.9.0", 24 | "express": "^4.13.4", 25 | "express-routebuilder": "^2.1.0", 26 | "knex": "^0.11.3", 27 | "pg": "^4.5.5", 28 | "request": "^2.72.0" 29 | }, 30 | "devDependencies": { 31 | "dotenv": "^2.0.0" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/npm/slack-hook-subscribe.git" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/npm/slack-hook-subscribe/issues" 39 | }, 40 | "homepage": "https://github.com/npm/slack-hook-subscribe#readme", 41 | "description": "" 42 | } 43 | -------------------------------------------------------------------------------- /seeds/0_users.js: -------------------------------------------------------------------------------- 1 | exports.seed = function(knex, Promise) { 2 | return Promise.join( 3 | knex('users').insert({ 4 | 'slack-user-id': '666', 5 | 'slack-team-id': '667' 6 | }) 7 | ); 8 | }; 9 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const port = process.env.PORT || '8080'; 2 | const host = process.env.HOST || '0.0.0.0'; 3 | const express = require('express'); 4 | const app = express(); 5 | 6 | app.use(require('./src')); 7 | 8 | app.listen(port, host); 9 | 10 | console.log('Server running on, %s:%d', host, port); 11 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const express = require('express'); 5 | const app = express(); 6 | 7 | const resourcesPath = path.join(__dirname, 'resources'); 8 | const resources = fs.readdirSync(resourcesPath); 9 | const User = require('./resources/users/model'); 10 | const Subscription = require('./resources/subscriptions/model'); 11 | 12 | const API = require('./lib/api'); 13 | 14 | app.get('/', function(req, res) { 15 | res.redirect('/v1'); 16 | }); 17 | 18 | app.get('/v1', function (request, response) { 19 | response.set('Content-Type', 'application/json'); 20 | response.send(JSON.stringify(API.index(), null, 2)); 21 | }); 22 | 23 | resources.forEach(function (resource) { 24 | API.register(resource); 25 | app.use(API.endpoint(resource)); 26 | }); 27 | 28 | module.exports = app; 29 | -------------------------------------------------------------------------------- /src/lib/api.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const express = require('express'); 4 | const routeBuilder = require('express-routebuilder'); 5 | 6 | const Endpoints = require('endpoints'); 7 | 8 | const router = express.Router(); 9 | 10 | module.exports = new Endpoints.Application({ 11 | searchPaths: [path.join(__dirname, '..', 'resources')], 12 | routeBuilder: function (routes, prefix) { 13 | return routeBuilder(router, routes, prefix); 14 | }, 15 | Controller: Endpoints.Controller.extend({ 16 | baseUrl: '/v1', 17 | store: Endpoints.Store.bookshelf, 18 | format: Endpoints.Format.jsonapi, 19 | validators: [Endpoints.ValidateJsonSchema] 20 | }) 21 | }); 22 | -------------------------------------------------------------------------------- /src/lib/base_model.js: -------------------------------------------------------------------------------- 1 | const Bookshelf = require('./database'); 2 | 3 | const instanceProps = {}; 4 | const classProps = { 5 | transaction: Bookshelf.transaction.bind(Bookshelf), 6 | exists: function(prop, value) { 7 | this.where(prop, value) 8 | .fetch() 9 | .then(function(model) { 10 | return model === undefined; 11 | }); 12 | } 13 | }; 14 | 15 | module.exports = Bookshelf.Model.extend(instanceProps, classProps); 16 | -------------------------------------------------------------------------------- /src/lib/base_routes.js: -------------------------------------------------------------------------------- 1 | function BaseRoutes(controller, schema) { 2 | this.controller = controller; 3 | this.schema = schema; 4 | } 5 | 6 | BaseRoutes.prototype.CRUD = function() { 7 | var controller = this.controller; 8 | var schema = this.schema; 9 | 10 | return { 11 | post: { 12 | '/': controller.create({ 13 | schema: schema 14 | }), 15 | '/:id/relationships/:relation': controller.createRelation() 16 | }, 17 | get: { 18 | '/': controller.read(), 19 | '/:id': controller.read(), 20 | '/:id/:related': controller.readRelated(), 21 | '/:id/relationships/:relation': controller.readRelation() 22 | } , 23 | patch: { 24 | '/:id': controller.update({ 25 | schema: schema 26 | }), 27 | '/:id/relationships/:relation': controller.updateRelation() 28 | }, 29 | delete: { 30 | '/:id': controller.destroy(), 31 | '/:id/relationships/:relation': controller.destroyRelation() 32 | } 33 | }; 34 | }; 35 | 36 | module.exports = BaseRoutes; 37 | -------------------------------------------------------------------------------- /src/lib/database.js: -------------------------------------------------------------------------------- 1 | require('dotenv').load(); 2 | const config = require('../../knexfile')[process.env.NODE_ENV || "development"]; 3 | 4 | const Knex = require('knex')(config); 5 | const Bookshelf = require('bookshelf')(Knex); 6 | 7 | module.exports = Bookshelf; 8 | -------------------------------------------------------------------------------- /src/lib/hook_receiver.js: -------------------------------------------------------------------------------- 1 | const makeReceiver = require("npm-hook-receiver"); 2 | 3 | // hook receiver is a restify server 4 | var opts = { 5 | name: process.env.SERVICE_NAME || 'hooks-subscriber-bot', 6 | secret: process.env.SHARED_SECRET, 7 | mount: process.env.MOUNT_POINT || '/incoming', 8 | }; 9 | var server = makeReceiver(opts); 10 | 11 | module.exports = server; 12 | -------------------------------------------------------------------------------- /src/lib/logger.js: -------------------------------------------------------------------------------- 1 | const bole = require("bole"); 2 | const logstring = require("common-log-string"); 3 | 4 | var logger = bole(process.env.SERVICE_NAME || 'hooks-bot'); 5 | bole.output({ level: 'info', stream: process.stdout }); 6 | 7 | module.exports = { 8 | logger: logger, 9 | logHandler: function logEachRequest(request, response, route, error) { 10 | logger.info(logstring(request, response)); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/lib/slack_client.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | const slack = require("@slack/client"); 3 | 4 | var token = process.env.SLACK_API_TOKEN || ''; 5 | assert(token, 'you must supply a slack api token in process.env.SLACK_API_TOKEN'); 6 | var channelID = process.env.SLACK_CHANNEL; 7 | assert(channelID, 'you must supply a slack channel ID in process.env.SLACK_CHANNEL'); 8 | 9 | module.exports = { 10 | client: new slack.WebClient(token), 11 | channelID: process.env.SLACK_CHANNEL 12 | }; 13 | -------------------------------------------------------------------------------- /src/ping.js: -------------------------------------------------------------------------------- 1 | module.exports = function(req, res, next) { 2 | res.send(200, 'pong'); 3 | next(); 4 | }; 5 | -------------------------------------------------------------------------------- /src/resources/subscriptions/controller.js: -------------------------------------------------------------------------------- 1 | const thisFolderName = __dirname.split('/').pop(); 2 | 3 | const API = require('../../lib/api'); 4 | 5 | module.exports = new API.Controller({ 6 | model: require('./model'), 7 | basePath: thisFolderName 8 | }); 9 | -------------------------------------------------------------------------------- /src/resources/subscriptions/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parseRequestBody: function (request) { 3 | var messages = request._body.split('&')[8].split('+'); 4 | var command = messages[0].replace("text=", ""); 5 | switch (command) { 6 | case "login": 7 | return { 8 | command: command, 9 | token: messages[1] 10 | }; 11 | case "help": 12 | return { 13 | command: command 14 | }; 15 | default: 16 | return { 17 | command: command, 18 | type: messages[1], 19 | name: messages[2], 20 | event: messages[3], 21 | secret: messages[4] 22 | }; 23 | } 24 | }, 25 | buildHookRequestOpts: function(info) { 26 | return { 27 | uri: 'https://registry.npmjs.org/-/npm/v1/hooks/hook', 28 | auth: { bearer: process.env.NPM_AUTH_TOKEN }, 29 | json: { 30 | type: info.type, 31 | name: info.name, 32 | endpoint: 'http://5c19aa89.ngrok.io/incoming', 33 | secret: process.env.SHARED_SECRET 34 | } 35 | }; 36 | }, 37 | buildSubscription: function(hook_opts, body) { 38 | return { 39 | "user_id": 1, // use dummy user for now 40 | "hook_id": body.id, 41 | "name": hook_opts.json.name, 42 | "type": hook_opts.json.type, 43 | "event": hook_opts.json.event 44 | } 45 | }, 46 | buildUser: function(userOpts, body) { 47 | return { 48 | 'slack-user-id': '666', 49 | 'slack-team-id': '667', 50 | 'npm-token-hashed': body.token 51 | }; 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/resources/subscriptions/messenger.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | buildRequestMessage: function (info) { 3 | return "Subscription request received for" + 4 | ' type: ' + info.type + ',' + 5 | ' name: ' + info.name + ',' + 6 | ' event: ' + info.event; 7 | }, 8 | buildSuccessMessage: function(record) { 9 | return 'Subscription successfully created! Captain-Hook ID: ' + record.get('id') + 10 | ' Webhooks ID: ' + record.get('hook_id'); 11 | }, 12 | bookshelfCreateError: 'ARRGH! Bookshelf error creating subscription', 13 | loggedIn: 'You are logged in!' 14 | }; 15 | -------------------------------------------------------------------------------- /src/resources/subscriptions/model.js: -------------------------------------------------------------------------------- 1 | const BaseModel = require('../../lib/base_model'); 2 | 3 | const instanceProps = { 4 | tableName: 'subscriptions', 5 | user: function () { 6 | return this.belongsTo(require('../users/model')); 7 | } 8 | }; 9 | 10 | const classProps = { 11 | typeName: 'subscriptions', 12 | filters: {}, 13 | relations: [ 14 | 'user' 15 | ] 16 | }; 17 | 18 | module.exports = BaseModel.extend(instanceProps, classProps); 19 | -------------------------------------------------------------------------------- /src/resources/subscriptions/routes.js: -------------------------------------------------------------------------------- 1 | const controller = require('./controller'); 2 | const schema = require('./schema'); 3 | const BaseRoutes = require('../../lib/base_routes.js'); 4 | 5 | var Router = new BaseRoutes(controller, schema); 6 | var routes = Router.CRUD(); 7 | 8 | exports.map = routes; 9 | -------------------------------------------------------------------------------- /src/resources/subscriptions/routes/slack.js: -------------------------------------------------------------------------------- 1 | const Request = require('request'); 2 | 3 | const slack = require('../../../lib/slack_client').client; 4 | const channelID = require('../../../lib/slack_client').channelID; 5 | 6 | const User = require("../../users/model"); 7 | const Subscription = require("../model"); 8 | 9 | const helpers = require("../helpers"); 10 | const messenger = require("../messenger"); 11 | 12 | const slackOpts = { 13 | as_user: true, 14 | username: "captainhook" 15 | }; 16 | 17 | const login = function(info) { 18 | var opts = { 19 | uri:'https://registry.npmjs.com/-/whoami', 20 | auth: { bearer: info.token } 21 | }; 22 | return Request.get(opts, function(err, res, body){ 23 | if(res.statusCode === 401) { 24 | slack.chat.postMessage(channelID, body, slackOpts); 25 | } 26 | 27 | return User.where('npm-token-hashed', info.token) 28 | .fetch({ 29 | require: false 30 | }) 31 | .then(function(user){ 32 | if( user == null ){ 33 | throw new Error("User not found"); 34 | } 35 | return user.set({'npm-token-hashed': info.token}).save(); 36 | }) 37 | .catch(function(){ 38 | var userOpts = helpers.buildUser(opts, body); 39 | return User.forge(userOpts).save(); 40 | }) 41 | .finally(function(){ 42 | slack.chat.postMessage(channelID, messenger.loggedIn, slackOpts); 43 | }); 44 | }); 45 | }; 46 | 47 | const logout = function(info) { 48 | // this is waiting on Slack App Integration 49 | }; 50 | 51 | var subscribe = function(info) { 52 | var hook_opts = helpers.buildHookRequestOpts(info); 53 | Request.post(hook_opts, function(err, res, body) { 54 | if (err) { 55 | slack.chat.postMessage(channelID, err.toString(), slackOpts); 56 | } else { 57 | console.log('just created hook with id=' + body.id); 58 | var subscription = helpers.buildSubscription(hook_opts, body); 59 | Subscription.forge(subscription).save() 60 | .then(function(record) { 61 | if (!record) { 62 | slack.chat.postMessage(channelID, messenger.bookshelf, slackOpts); 63 | } else { 64 | var message = messenger.buildSuccessMessage(record); 65 | slack.chat.postMessage(channelID, message, slackOpts); 66 | } 67 | }) 68 | .catch(); 69 | } 70 | }); 71 | }; 72 | 73 | var unsubscribe = function(info) { 74 | Subscription.where({ type: info.type, name: info.name }).fetch() 75 | .then(function(record) { 76 | var hook_opts = { 77 | uri: "https://registry.npmjs.org/-/npm/v1/hooks/hook/" + record.attributes.hook_id, 78 | auth: { bearer: process.env.NPM_AUTH_TOKEN } 79 | }; 80 | Request.delete(hook_opts, function(err, res, body) { 81 | if (err) { 82 | slack.chat.postMessage(channelID, body, slackOpts); 83 | } else { 84 | record.destroy() 85 | .then(function(record) { 86 | slack.chat.postMessage(channelID, "Subscription successfully deleted!", slackOpts); 87 | }) 88 | .catch(); 89 | } 90 | }); 91 | }) 92 | .catch(); 93 | }; 94 | 95 | var list = function(info) { 96 | Subscription.where({ 'user_id': 1}).fetchAll() 97 | .then(function(collection) { 98 | console.log(collection); 99 | var message = "Your Hooks:\n" + 100 | "*id*\t\t*type*\t\t*name*\t\t*event*\n"; 101 | var subscriptions = collection.models; 102 | for (var i = 0; i < subscriptions.length; i++) { 103 | var subscription = subscriptions[i].attributes; 104 | console.log(subscription); 105 | message += subscription.id + "\t\t" + 106 | subscription.type + "\t\t" + 107 | subscription.name + "\t\t" + 108 | subscription.event + "\n"; 109 | } 110 | slack.chat.postMessage(channelID, message, slackOpts); 111 | }) 112 | .catch(); 113 | }; 114 | 115 | var help = function() { 116 | var message = "arrrr! i'm captain hook\n" + 117 | "*usage:* \n" + 118 | "`/captain-hook `\n" + 119 | "\n" + 120 | "\t\t*command*: `subscribe` (create a new webhook), `help`, `list`\n" + 121 | "\t\t*type*: `package` or `scope`\n" + 122 | "\t\t*name*: the name of the package or scope, e.g. `lodash`\n" + 123 | "\t\t*event*: this doesn't actually work yet :grimacing: :sweat_smile:\n" + 124 | "\n" + 125 | "\n" + 126 | "/captain-hook login "; 127 | 128 | slack.chat.postMessage(channelID, message, slackOpts); 129 | }; 130 | 131 | // receive outgoing integration from slack `/captain-hook` 132 | module.exports = function(request, response, next) { 133 | var info = helpers.parseRequestBody(request); 134 | switch(info.command) { 135 | case "login": 136 | login(info); 137 | break; 138 | case "subscribe": 139 | subscribe(info); 140 | break; 141 | case "unsubscribe": 142 | unsubscribe(info); 143 | break; 144 | case "list": 145 | list(info); 146 | break; 147 | case "help": 148 | help(); 149 | break; 150 | default: 151 | help(); 152 | break; 153 | } 154 | next(); 155 | }; 156 | -------------------------------------------------------------------------------- /src/resources/subscriptions/schema.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | body: { 3 | properties: { 4 | id: { 5 | type: 'integer' 6 | } 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/resources/users/controller.js: -------------------------------------------------------------------------------- 1 | const thisFolderName = __dirname.split('/').pop(); 2 | 3 | const API = require('../../lib/api'); 4 | 5 | module.exports = new API.Controller({ 6 | model: require('./model'), 7 | basePath: thisFolderName 8 | }); 9 | -------------------------------------------------------------------------------- /src/resources/users/model.js: -------------------------------------------------------------------------------- 1 | const BaseModel = require('../../lib/base_model'); 2 | 3 | const instanceProps = { 4 | tableName: 'users', 5 | subscriptions: function() { 6 | return this.hasMany(require('../subscriptions/model')); 7 | } 8 | }; 9 | 10 | const classProps = { 11 | typeName: 'users', 12 | filters: {}, 13 | relations: [ 14 | 'subscriptions' 15 | ] 16 | }; 17 | 18 | module.exports = BaseModel.extend(instanceProps, classProps); 19 | -------------------------------------------------------------------------------- /src/resources/users/routes.js: -------------------------------------------------------------------------------- 1 | const controller = require('./controller'); 2 | const schema = require('./schema'); 3 | const BaseRoutes = require('../../lib/base_routes.js'); 4 | 5 | var Router = new BaseRoutes(controller, schema); 6 | var routes = Router.CRUD(); 7 | 8 | exports.map = routes; 9 | -------------------------------------------------------------------------------- /src/resources/users/schema.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | body: { 3 | properties: { 4 | id: { 5 | type: 'integer' 6 | } 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ping: require('./ping'), 3 | subscriptions: { 4 | slack: require('./resources/subscriptions/routes/slack') 5 | } 6 | }; 7 | --------------------------------------------------------------------------------