Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GSoC19] Articles comment #4

Open
wants to merge 11 commits into
base: articles
Choose a base branch
from
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.
89 changes: 89 additions & 0 deletions app/articles/server/api/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
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 = Models.Users.findOne({
_id: this.bodyParams.userId,
});

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();
}

// this.bodyParams.bot = { i: this.integration._id };

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);
}
}

Api.addRoute(':blogId/:token', { authRequired: true }, {
post: executeAnnouncementRest,
get: executeAnnouncementRest,
});
4 changes: 4 additions & 0 deletions app/articles/server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import './settings';
import './methods/admin';
import './methods/user';
import './api/api';
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 collabToken = `${ blogToken }/${ Random.id(24) }`;
settings.updateById('Announcement_Token', announceToken);
settings.updateById('Collaboration_Token', collabToken);
const data = {
setup: [{
rc_url: rcUrl,
rc_id: user._id,
rc_token: token,
name: user.name,
email: getVerifiedEmail(user.emails),
announce_token: announceToken,
collaboration_token: collabToken,
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());
let errMsg = 'Unable to connect to Ghost. Make sure Ghost is running';

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

if (response.data.setup[0].status) { // Ghost site is already setup
return redirectGhost();
}

// Setup Ghost Site and set title
response = setupGhost(user, token);
errMsg = 'Unable to setup. Make sure Ghost is running';

if (response.statusCode === 201 && response.content) {
return redirectGhost();
}

throw new Meteor.Error(errMsg);
} catch (e) {
throw new Meteor.Error(e.error || errMsg);
}
},
});
85 changes: 85 additions & 0 deletions app/articles/server/methods/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Meteor } from 'meteor/meteor';
import { HTTP } from 'meteor/http';

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

const api = new API();

function addUser(user, accessToken) {
const data = {
user: [{
rc_username: user.username,
role: 'Author', // User can add itself only as Author, even if he/she is admin in RC
rc_uid: user._id,
rc_token: accessToken,
}],
};
return HTTP.call('POST', api.createAccount(), { data, headers: { 'Content-Type': 'application/json' } });
}

function userExist(user, accessToken) {
const data = {
user: [{
rc_uid: user._id,
rc_token: accessToken,
}],
};
const response = HTTP.call('GET', api.userExist(), { data, headers: { 'Content-Type': 'application/json' } });
return response.data && response.data.users[0] && response.data.users[0].exist;
}

function inviteSetting() {
const response = HTTP.call('GET', api.invite());
const { settings } = response.data;

if (settings && settings[0] && settings[0].key === 'invite_only') {
return settings[0].value;
}
// default value in Ghost
return false;
}

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

Meteor.methods({
redirectUserToArticles(accessToken) {
const enabled = settings.get('Articles_enabled');

if (!enabled) {
throw new Meteor.Error('Articles are disabled');
}
const user = Meteor.users.findOne(Meteor.userId());
let errMsg = 'Ghost is not set up. Setup can be done from Admin Panel';

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

if (response.data.setup[0].status) { // Ghost site is already setup
// user exist in ghost
if (userExist(user, accessToken)) {
return redirectGhost();
}

const inviteOnly = inviteSetting();

// create user account in ghost
if (!inviteOnly && addUser(user, accessToken).statusCode === 200) {
return redirectGhost();
}

errMsg = inviteOnly ? 'You are not a member of Ghost. Ask admin to add' : 'Unable to setup your account';
}

// Cannot setup Ghost from sidenav
throw new Meteor.Error(errMsg);
} catch (e) {
throw new Meteor.Error(e.error || 'Unable to connect to Ghost.');
}
},
});
64 changes: 64 additions & 0 deletions app/articles/server/settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Meteor } from 'meteor/meteor';

import { settings } from '../../settings';

const defaults = {
enable: false,
};

Meteor.startup(() => {
settings.addGroup('Articles', function() {
this.add('Articles_enabled', defaults.enable, {
type: 'boolean',
i18nLabel: 'Enable',
public: true,
});

this.add('Article_Site_title', 'Rocket.Chat', {
type: 'string',
enableQuery: {
_id: 'Articles_enabled',
value: true,
},
public: true,
});

this.add('Article_Site_Url', 'http://localhost:2368', {
type: 'string',
enableQuery: {
_id: 'Articles_enabled',
value: true,
},
public: true,
});

this.add('Announcement_Token', 'announcement_token', {
type: 'string',
readonly: true,
enableQuery: {
_id: 'Articles_enabled',
value: true,
},
public: true,
});

this.add('Collaboration_Token', 'collaboration_token', {
type: 'string',
readonly: true,
enableQuery: {
_id: 'Articles_enabled',
value: true,
},
public: true,
});

this.add('Articles_admin_panel', 'Articles_admin_panel', {
type: 'link',
enableQuery: {
_id: 'Articles_enabled',
value: true,
},
linkText: 'Article_Admin_Panel',
});
});
});
Loading