Post to Slack (#603)
* Migrations
* WIP: Integration model, slack perms / hooks
* So so rough it pains me. Building this new model is revealing just how much needs to be refactored
* Working connect and post
* Cleanup UI, upating documents
* Show when slack command is connected
* stash
* 💚
* Add documents.update trigger
* Authorization, tidying
* Fixed integration policy
* pick integration presenter keys
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import Router from 'koa-router';
|
||||
import auth from './middlewares/authentication';
|
||||
import { presentUser, presentTeam } from '../presenters';
|
||||
import { Authentication, User, Team } from '../models';
|
||||
import { Authentication, Integration, User, Team } from '../models';
|
||||
import * as Slack from '../slack';
|
||||
|
||||
const router = new Router();
|
||||
@@ -89,14 +89,56 @@ router.post('auth.slackCommands', auth(), async ctx => {
|
||||
const user = ctx.state.user;
|
||||
const endpoint = `${process.env.URL || ''}/auth/slack/commands`;
|
||||
const data = await Slack.oauthAccess(code, endpoint);
|
||||
const serviceId = 'slack';
|
||||
|
||||
await Authentication.create({
|
||||
serviceId: 'slack',
|
||||
const authentication = await Authentication.create({
|
||||
serviceId,
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
token: data.access_token,
|
||||
scopes: data.scope.split(','),
|
||||
});
|
||||
|
||||
await Integration.create({
|
||||
serviceId,
|
||||
type: 'command',
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
authenticationId: authentication.id,
|
||||
});
|
||||
});
|
||||
|
||||
router.post('auth.slackPost', auth(), async ctx => {
|
||||
const { code, collectionId } = ctx.body;
|
||||
ctx.assertPresent(code, 'code is required');
|
||||
|
||||
const user = ctx.state.user;
|
||||
const endpoint = `${process.env.URL || ''}/auth/slack/post`;
|
||||
const data = await Slack.oauthAccess(code, endpoint);
|
||||
const serviceId = 'slack';
|
||||
|
||||
const authentication = await Authentication.create({
|
||||
serviceId,
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
token: data.access_token,
|
||||
scopes: data.scope.split(','),
|
||||
});
|
||||
|
||||
await Integration.create({
|
||||
serviceId,
|
||||
type: 'post',
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
authenticationId: authentication.id,
|
||||
collectionId,
|
||||
events: [],
|
||||
settings: {
|
||||
url: data.incoming_webhook.url,
|
||||
channel: data.incoming_webhook.channel,
|
||||
channelId: data.incoming_webhook.channel_id,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -5,7 +5,8 @@ import auth from './middlewares/authentication';
|
||||
import pagination from './middlewares/pagination';
|
||||
import { presentDocument, presentRevision } from '../presenters';
|
||||
import { Document, Collection, Star, View, Revision } from '../models';
|
||||
import { ValidationError, InvalidRequestError } from '../errors';
|
||||
import { InvalidRequestError } from '../errors';
|
||||
import events from '../events';
|
||||
import policy from '../policies';
|
||||
|
||||
const { authorize } = policy;
|
||||
@@ -302,7 +303,6 @@ router.post('documents.create', auth(), async ctx => {
|
||||
authorize(user, 'read', parentDocumentObj);
|
||||
}
|
||||
|
||||
const publishedAt = publish === false ? null : new Date();
|
||||
let document = await Document.create({
|
||||
parentDocumentId: parentDocumentObj.id,
|
||||
atlasId: collection.id,
|
||||
@@ -310,20 +310,19 @@ router.post('documents.create', auth(), async ctx => {
|
||||
userId: user.id,
|
||||
lastModifiedById: user.id,
|
||||
createdById: user.id,
|
||||
publishedAt,
|
||||
title,
|
||||
text,
|
||||
});
|
||||
|
||||
if (publishedAt && collection.type === 'atlas') {
|
||||
await collection.addDocumentToStructure(document, index);
|
||||
if (publish) {
|
||||
await document.publish();
|
||||
}
|
||||
|
||||
// reload to get all of the data needed to present (user, collection etc)
|
||||
// we need to specify publishedAt to bypass default scope that only returns
|
||||
// published documents
|
||||
document = await Document.find({
|
||||
where: { id: document.id, publishedAt },
|
||||
where: { id: document.id, publishedAt: document.publishedAt },
|
||||
});
|
||||
|
||||
ctx.body = {
|
||||
@@ -332,7 +331,7 @@ router.post('documents.create', auth(), async ctx => {
|
||||
});
|
||||
|
||||
router.post('documents.update', auth(), async ctx => {
|
||||
const { id, title, text, publish, lastRevision } = ctx.body;
|
||||
const { id, title, text, publish, done, lastRevision } = ctx.body;
|
||||
ctx.assertPresent(id, 'id is required');
|
||||
ctx.assertPresent(title || text, 'title or text is required');
|
||||
|
||||
@@ -346,24 +345,20 @@ router.post('documents.update', auth(), async ctx => {
|
||||
}
|
||||
|
||||
// Update document
|
||||
const previouslyPublished = !!document.publishedAt;
|
||||
if (publish) document.publishedAt = new Date();
|
||||
if (title) document.title = title;
|
||||
if (text) document.text = text;
|
||||
document.lastModifiedById = user.id;
|
||||
|
||||
await document.save();
|
||||
const collection = document.collection;
|
||||
if (collection.type === 'atlas') {
|
||||
if (previouslyPublished) {
|
||||
await collection.updateDocument(document);
|
||||
} else if (publish) {
|
||||
await collection.addDocumentToStructure(document);
|
||||
if (publish) {
|
||||
await document.publish();
|
||||
} else {
|
||||
await document.save();
|
||||
|
||||
if (document.publishedAt && done) {
|
||||
events.add({ name: 'documents.update', model: document });
|
||||
}
|
||||
}
|
||||
|
||||
document.collection = collection;
|
||||
|
||||
ctx.body = {
|
||||
data: await presentDocument(ctx, document),
|
||||
};
|
||||
|
||||
@@ -385,12 +385,7 @@ describe('#documents.create', async () => {
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
const newDocument = await Document.findOne({
|
||||
where: {
|
||||
id: body.data.id,
|
||||
},
|
||||
});
|
||||
|
||||
const newDocument = await Document.findById(body.data.id);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(newDocument.parentDocumentId).toBe(null);
|
||||
expect(newDocument.collection.id).toBe(collection.id);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import Router from 'koa-router';
|
||||
import { AuthenticationError, InvalidRequestError } from '../errors';
|
||||
import { Authentication, Document, User } from '../models';
|
||||
import { presentSlackAttachment } from '../presenters';
|
||||
import * as Slack from '../slack';
|
||||
const router = new Router();
|
||||
|
||||
@@ -67,14 +68,7 @@ router.post('hooks.slack', async ctx => {
|
||||
if (documents.length) {
|
||||
const attachments = [];
|
||||
for (const document of documents) {
|
||||
attachments.push({
|
||||
color: document.collection.color,
|
||||
title: document.title,
|
||||
title_link: `${process.env.URL}${document.getUrl()}`,
|
||||
footer: document.collection.name,
|
||||
text: document.getSummary(),
|
||||
ts: document.getTimestamp(),
|
||||
});
|
||||
attachments.push(presentSlackAttachment(document));
|
||||
}
|
||||
|
||||
ctx.body = {
|
||||
|
||||
@@ -13,6 +13,7 @@ import views from './views';
|
||||
import hooks from './hooks';
|
||||
import apiKeys from './apiKeys';
|
||||
import team from './team';
|
||||
import integrations from './integrations';
|
||||
|
||||
import validation from './middlewares/validation';
|
||||
import methodOverride from '../middlewares/methodOverride';
|
||||
@@ -74,6 +75,7 @@ router.use('/', views.routes());
|
||||
router.use('/', hooks.routes());
|
||||
router.use('/', apiKeys.routes());
|
||||
router.use('/', team.routes());
|
||||
router.use('/', integrations.routes());
|
||||
|
||||
// Router is embedded in a Koa application wrapper, because koa-router does not
|
||||
// allow middleware to catch any routes which were not explicitly defined.
|
||||
|
||||
48
server/api/integrations.js
Normal file
48
server/api/integrations.js
Normal file
@@ -0,0 +1,48 @@
|
||||
// @flow
|
||||
import Router from 'koa-router';
|
||||
import Integration from '../models/Integration';
|
||||
import pagination from './middlewares/pagination';
|
||||
import auth from './middlewares/authentication';
|
||||
import { presentIntegration } from '../presenters';
|
||||
import policy from '../policies';
|
||||
|
||||
const { authorize } = policy;
|
||||
const router = new Router();
|
||||
|
||||
router.post('integrations.list', auth(), pagination(), async ctx => {
|
||||
let { sort = 'updatedAt', direction } = ctx.body;
|
||||
if (direction !== 'ASC') direction = 'DESC';
|
||||
|
||||
const user = ctx.state.user;
|
||||
const integrations = await Integration.findAll({
|
||||
where: { teamId: user.teamId },
|
||||
order: [[sort, direction]],
|
||||
offset: ctx.state.pagination.offset,
|
||||
limit: ctx.state.pagination.limit,
|
||||
});
|
||||
|
||||
const data = await Promise.all(
|
||||
integrations.map(integration => presentIntegration(ctx, integration))
|
||||
);
|
||||
|
||||
ctx.body = {
|
||||
pagination: ctx.state.pagination,
|
||||
data,
|
||||
};
|
||||
});
|
||||
|
||||
router.post('integrations.delete', auth(), async ctx => {
|
||||
const { id } = ctx.body;
|
||||
ctx.assertPresent(id, 'id is required');
|
||||
|
||||
const integration = await Integration.findById(id);
|
||||
authorize(ctx.state.user, 'delete', integration);
|
||||
|
||||
await integration.destroy();
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
};
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user