Skip to content

Commit

Permalink
Articles (#63)
Browse files Browse the repository at this point in the history
* articles commits sqashed in one

* fix import error
  • Loading branch information
Kailash0311 authored Jul 15, 2019
1 parent 4b99331 commit 62fca65
Show file tree
Hide file tree
Showing 31 changed files with 871 additions and 14 deletions.
3 changes: 2 additions & 1 deletion app/api/server/v1/rooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ API.v1.addRoute('rooms.leave', { authRequired: true }, {

API.v1.addRoute('rooms.createDiscussion', { authRequired: true }, {
post() {
const { prid, pmid, reply, t_name, users } = this.bodyParams;
const { prid, pmid, reply, t_name, users, t } = this.bodyParams;
if (!prid) {
return API.v1.failure('Body parameter "prid" is required.');
}
Expand All @@ -238,6 +238,7 @@ API.v1.addRoute('rooms.createDiscussion', { authRequired: true }, {
pmid,
t_name,
reply,
t,
users: users || [],
}));

Expand Down
1 change: 1 addition & 0 deletions app/articles/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Readme file for articles.
Empty file added app/articles/client/index.js
Empty file.
Empty file added app/articles/index.js
Empty file.
104 changes: 104 additions & 0 deletions app/articles/server/api/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { Restivus } from 'meteor/nimble:restivus';
import _ from 'underscore';

import { processWebhookMessage } from '../../../lib';
import { API } from '../../../api';
import { settings } from '../../../settings';
import * as Models from '../../../models';

const Api = new Restivus({
enableCors: true,
apiPath: 'ghooks/',
auth: {
user() {
const payloadKeys = Object.keys(this.bodyParams);
const payloadIsWrapped = (this.bodyParams && this.bodyParams.payload) && payloadKeys.length === 1;
if (payloadIsWrapped && this.request.headers['content-type'] === 'application/x-www-form-urlencoded') {
try {
this.bodyParams = JSON.parse(this.bodyParams.payload);
} catch ({ message }) {
return {
error: {
statusCode: 400,
body: {
success: false,
error: message,
},
},
};
}
}

this.announceToken = settings.get('Announcement_Token');
const { blogId } = this.request.params;
const token = decodeURIComponent(this.request.params.token);

if (this.announceToken !== `${ blogId }/${ token }`) {
return {
error: {
statusCode: 404,
body: {
success: false,
error: 'Invalid token provided.',
},
},
};
}

const user = this.bodyParams.userId ? Models.Users.findOne({ _id: this.bodyParams.userId }) : Models.Users.findOne({ username: this.bodyParams.username });

return { user };
},
},
});

function executeAnnouncementRest() {
const defaultValues = {
channel: this.bodyParams.channel,
alias: this.bodyParams.alias,
avatar: this.bodyParams.avatar,
emoji: this.bodyParams.emoji,
};

// TODO: Turn this into an option on the integrations - no body means a success
// TODO: Temporary fix for https://github.com/RocketChat/Rocket.Chat/issues/7770 until the above is implemented
if (!this.bodyParams || (_.isEmpty(this.bodyParams) && !this.integration.scriptEnabled)) {
// return RocketChat.API.v1.failure('body-empty');
return API.v1.success();
}

try {
const message = processWebhookMessage(this.bodyParams, this.user, defaultValues);
if (_.isEmpty(message)) {
return API.v1.failure('unknown-error');
}

return API.v1.success();
} catch ({ error, message }) {
return API.v1.failure(error || message);
}
}

function executefetchUserRest() {
try {
const { _id, name, username, emails } = this.user;
const user = { _id, name, username, emails };

return API.v1.success({ user });
} catch ({ error, message }) {
return API.v1.failure(error || message);
}
}

Api.addRoute(':blogId/:token', { authRequired: true }, {
post: executeAnnouncementRest,
get: executeAnnouncementRest,
});

// If a user is editor/admin in Ghost but is not an admin in RC,
// then the e-mail will not be provided to that user
// This method will allow user to fetch user with email.
Api.addRoute(':blogId/:token/getUser', { authRequired: true }, {
post: executefetchUserRest,
get: executefetchUserRest,
});
6 changes: 6 additions & 0 deletions app/articles/server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import './settings';
import './methods/admin';
import './methods/user';
import './api/api';
import './lib/triggerHandler';
import './triggers';
150 changes: 150 additions & 0 deletions app/articles/server/lib/triggerHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { Meteor } from 'meteor/meteor';
import { HTTP } from 'meteor/http';

import { settings } from '../../../settings';
import { API } from '../utils/url';

const api = new API();

export const triggerHandler = new class ArticlesSettingsHandler {
constructor() {
this.trigger = {};
}

eventNameArgumentsToObject(...args) {
const argObject = {
event: args[0],
};
switch (argObject.event) {
case 'userEmail':
case 'userRealname':
case 'userAvatar':
case 'userName':
if (args.length >= 2) {
argObject.user = args[1];
}
break;
case 'roomType':
case 'roomName':
if (args.length >= 2) {
argObject.room = args[1];
}
break;
case 'siteTitle':
argObject.article = args[1];
break;
default:
argObject.event = undefined;
break;
}
return argObject;
}

mapEventArgsToData(data, { event, room, user, article }) {
data.event = event;
switch (event) {
case 'userEmail':
case 'userRealname':
case 'userAvatar':
case 'userName':
data.user_id = user._id;

if (user.avatar) {
data.avatar = user.avatar;
}

if (user.name) {
data.name = user.name;
}

if (user.email) {
data.email = user.email;
}

if (user.username) {
data.username = user.username;
}
break;
case 'roomType':
case 'roomName':
data.room_id = room.rid;

if (room.name) {
data.name = room.name;
}

if (room.type) {
data.type = room.type;
}
break;
case 'siteTitle':
if (article && article.title) {
data.title = article.title;
}
break;
default:
break;
}
}

executeTrigger(...args) {
const argObject = this.eventNameArgumentsToObject(...args);
const { event } = argObject;

if (!event) {
return;
}

if (settings.get('Articles_enabled')) {
const token = settings.get('Settings_Token');
this.trigger.api = api.rhooks(token);
this.trigger.retryCount = 5;
}

this.executeTriggerUrl(argObject, 0);
}

executeTriggerUrl({ event, room, user, article }, tries = 0) {
if (!this.trigger.api) {
return;
}
const url = this.trigger.api;

const data = {};

this.mapEventArgsToData(data, { event, room, user, article });

const opts = {
params: {},
method: 'POST',
url,
data,
auth: undefined,
npmRequestOptions: {
rejectUnauthorized: !settings.get('Allow_Invalid_SelfSigned_Certs'),
strictSSL: !settings.get('Allow_Invalid_SelfSigned_Certs'),
},
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36',
},
};

if (!opts.url || !opts.method) {
return;
}

HTTP.call(opts.method, opts.url, opts, (error, result) => {
// if the result contained nothing or wasn't a successful statusCode
if (!result) {
if (tries < this.trigger.retryCount) {
// 2 seconds, 4 seconds, 8 seconds
const waitTime = Math.pow(2, tries + 1) * 1000;

Meteor.setTimeout(() => {
this.executeTriggerUrl({ event, room, user }, tries + 1);
}, waitTime);
}
}
});
}
}();
19 changes: 19 additions & 0 deletions app/articles/server/logoutCleanUp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Meteor } from 'meteor/meteor';
import { HTTP } from 'meteor/http';

import { settings } from '../../settings';
import { API } from './utils/url';

const api = new API();

export function ghostCleanUp(cookie) {
const rcUrl = Meteor.absoluteUrl().replace(/\/$/, '');
try {
if (settings.get('Articles_enabled')) {
HTTP.call('DELETE', api.session(), { headers: { cookie, referer: rcUrl } });
}
} catch (e) {
// Do nothing if failed to logout from Ghost.
// Error will be because user has not logged in to Ghost.
}
}
77 changes: 77 additions & 0 deletions app/articles/server/methods/admin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Meteor } from 'meteor/meteor';
import { HTTP } from 'meteor/http';
import _ from 'underscore';
import { Random } from 'meteor/random';

import { API } from '../utils/url';
import { settings } from '../../../settings';

const api = new API();

// Try to get a verified email, if available.
function getVerifiedEmail(emails) {
const email = _.find(emails, (e) => e.verified);
return email || emails[0].address;
}

function setupGhost(user, token) {
const rcUrl = Meteor.absoluteUrl().replace(/\/$/, '');
const blogTitle = settings.get('Article_Site_title');
const blogToken = Random.id(17);
const announceToken = `${ blogToken }/${ Random.id(24) }`;
const settingsToken = `${ blogToken }/${ Random.id(24) }`;
settings.updateById('Announcement_Token', announceToken);
settings.updateById('Settings_Token', settingsToken);
const data = {
setup: [{
rc_url: rcUrl,
rc_id: user._id,
rc_token: token,
name: user.name,
email: getVerifiedEmail(user.emails),
announce_token: announceToken,
settings_token: settingsToken,
blogTitle,
}],
};
return HTTP.call('POST', api.setup(), { data, headers: { 'Content-Type': 'application/json' } });
}

function redirectGhost() {
return {
link: api.siteUrl(),
message: 'Ghost is Set up. Redirecting.',
};
}

Meteor.methods({
Articles_admin_panel(token) {
const enabled = settings.get('Articles_enabled');

if (!enabled) {
throw new Meteor.Error('Articles are disabled');
}
const user = Meteor.users.findOne(Meteor.userId());

try {
let response = HTTP.call('GET', api.setup());

if (response.data && response.data.setup && response.data.setup[0]) {
if (response.data.setup[0].status) { // Ghost site is already setup
return redirectGhost();
} // Setup Ghost Site and set title
response = setupGhost(user, token);
if (response.statusCode === 201 && response.content) {
return redirectGhost();
} if (response.errors) {
throw new Meteor.Error(response.errors.message || 'Unable to setup. Make sure Ghost is running');
}
} else {
throw new Meteor.Error('Unable to redirect. Make sure Ghost is running.');
}
} catch (e) {
console.log(e);
throw new Meteor.Error(e.error || 'Unable to connect to Ghost. Make sure Ghost is running.');
}
},
});
Loading

0 comments on commit 62fca65

Please sign in to comment.