This repository has been archived on 2022-08-14. You can view files and clone it, but cannot push or open issues or pull requests.
outline/server/api/hooks.js

176 lines
4.8 KiB
JavaScript
Raw Normal View History

// @flow
2016-08-23 06:37:01 +00:00
import Router from 'koa-router';
import { escapeRegExp } from 'lodash';
2018-02-25 04:44:13 +00:00
import { AuthenticationError, InvalidRequestError } from '../errors';
import { Authentication, Document, User, Team } from '../models';
import { presentSlackAttachment } from '../presenters';
import * as Slack from '../slack';
2016-08-23 06:37:01 +00:00
const router = new Router();
// triggered by a user posting a getoutline.com link in Slack
router.post('hooks.unfurl', async ctx => {
const { challenge, token, event } = ctx.body;
if (challenge) return (ctx.body = ctx.body.challenge);
if (token !== process.env.SLACK_VERIFICATION_TOKEN) {
2018-02-25 04:44:13 +00:00
throw new AuthenticationError('Invalid token');
}
const user = await User.findOne({
2018-05-29 03:31:53 +00:00
where: { service: 'slack', serviceId: event.user },
});
if (!user) return;
const auth = await Authentication.findOne({
2018-06-01 19:13:05 +00:00
where: { service: 'slack', teamId: user.teamId },
});
if (!auth) return;
// get content for unfurled links
let unfurls = {};
for (let link of event.links) {
const id = link.url.substr(link.url.lastIndexOf('/') + 1);
const doc = await Document.findByPk(id);
if (!doc || doc.teamId !== user.teamId) continue;
unfurls[link.url] = {
title: doc.title,
text: doc.getSummary(),
color: doc.collection.color,
};
}
await Slack.post('chat.unfurl', {
token: auth.token,
channel: event.channel,
ts: event.message_ts,
unfurls,
});
});
// triggered by interactions with actions, dialogs, message buttons in Slack
router.post('hooks.interactive', async ctx => {
const { payload } = ctx.body;
ctx.assertPresent(payload, 'payload is required');
const data = JSON.parse(payload);
const { callback_id, token } = data;
ctx.assertPresent(token, 'token is required');
ctx.assertPresent(callback_id, 'callback_id is required');
if (token !== process.env.SLACK_VERIFICATION_TOKEN)
throw new AuthenticationError('Invalid verification token');
const user = await User.findOne({
where: { service: 'slack', serviceId: data.user.id },
});
if (!user) {
ctx.body = {
text: 'Sorry, we couldnt find your user on this team in Outline.',
response_type: 'ephemeral',
replace_original: false,
};
return;
}
// we find the document based on the users teamId to ensure access
const document = await Document.findOne({
where: { id: data.callback_id, teamId: user.teamId },
});
if (!document) throw new InvalidRequestError('Invalid document');
const team = await Team.findByPk(user.teamId);
// respond with a public message that will be posted in the original channel
ctx.body = {
response_type: 'in_channel',
replace_original: false,
attachments: [
presentSlackAttachment(document, team, document.getSummary()),
],
};
});
// triggered by the /outline command in Slack
2017-04-27 04:47:03 +00:00
router.post('hooks.slack', async ctx => {
const { token, user_id, text } = ctx.body;
2016-08-23 06:37:01 +00:00
ctx.assertPresent(token, 'token is required');
ctx.assertPresent(user_id, 'user_id is required');
if (token !== process.env.SLACK_VERIFICATION_TOKEN) {
throw new AuthenticationError('Invalid verification token');
}
// Handle "help" command or no input
if (text.trim() === 'help' || !text.trim()) {
ctx.body = {
response_type: 'ephemeral',
text: 'How to use /outline',
attachments: [
{
text:
'To search your knowledgebase use `/outline keyword`. \nYouve already learned how to get help with `/outline help`.',
},
],
};
return;
}
2016-08-23 06:37:01 +00:00
const user = await User.findOne({
2016-08-23 06:37:01 +00:00
where: {
2018-05-29 03:31:53 +00:00
service: 'slack',
serviceId: user_id,
2016-08-23 06:37:01 +00:00
},
});
if (!user) {
ctx.body = {
response_type: 'ephemeral',
text: 'Sorry, we couldnt find your user have you signed into Outline?',
};
return;
}
2016-08-23 06:37:01 +00:00
const team = await Team.findByPk(user.teamId);
const results = await Document.searchForUser(user, text, {
2016-08-23 06:37:01 +00:00
limit: 5,
});
if (results.length) {
const attachments = [];
for (const result of results) {
2018-08-05 07:51:47 +00:00
const queryIsInTitle = !!result.document.title
.toLowerCase()
.match(escapeRegExp(text.toLowerCase()));
2018-08-05 07:51:47 +00:00
attachments.push(
presentSlackAttachment(
result.document,
team,
queryIsInTitle ? undefined : result.context,
process.env.SLACK_MESSAGE_ACTIONS
? [
{
name: 'post',
text: 'Post to Channel',
type: 'button',
value: result.document.id,
},
]
: undefined
2018-08-05 07:51:47 +00:00
)
);
}
ctx.body = {
text: `This is what we found for "${text}"…`,
attachments,
};
} else {
ctx.body = {
text: `No results for "${text}"`,
};
2016-08-23 06:37:01 +00:00
}
});
export default router;