Skip to content
This repository has been archived by the owner on Oct 1, 2019. It is now read-only.

Commit

Permalink
Implement handling of "story" posts on back-end
Browse files Browse the repository at this point in the history
  • Loading branch information
voidxnull committed Jun 16, 2017
1 parent 01c5baa commit 19730c0
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 12 deletions.
12 changes: 12 additions & 0 deletions migrations/20170615170503_posts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export async function up(knex) {
await knex.schema.table('posts', table => {
table.text('html');
table.string('text_type');
});
}

export async function down(knex) {
await knex.schema.table('posts', table => {
table.dropColumns(['html', 'text_type']);
});
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"redux": "~3.6.0",
"redux-immutablejs": "0.0.8",
"reselect": "~3.0.0",
"sanitize-html": "^1.14.1",
"sendgrid": "^2.0.0",
"slug": "^0.9.1",
"smoothscroll-polyfill": "^0.3.4",
Expand Down
28 changes: 16 additions & 12 deletions src/api/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -1613,7 +1613,8 @@ export default class ApiController {

const typeRequirements = {
short_text: ['text'],
long_text: ['title', 'text']
long_text: ['title', 'text'],
story: ['title', 'text', 'text_type']
};

if (!(ctx.request.body.type in typeRequirements)) {
Expand Down Expand Up @@ -1679,15 +1680,14 @@ export default class ApiController {
const obj = new Post({
id: uuid.v4(),
type: ctx.request.body.type,
user_id: ctx.session.user
user_id: ctx.session.user,
text: ctx.request.body.text,
text_type: ctx.request.body.text_type // It'll be set automatically before saving if it's not present.
});

const more = {};

if (ctx.request.body.type === 'short_text') {
obj.set('text', ctx.request.body.text);
} else if (ctx.request.body.type === 'long_text') {
obj.set('text', ctx.request.body.text);
if (ctx.request.body.type === 'long_text') {
more.title = ctx.request.body.title;
}

Expand Down Expand Up @@ -1724,7 +1724,7 @@ export default class ApiController {
// Add the author to the list of subscribers by default.
obj.subscribers().attach(ctx.session.user);

await obj.fetch({ require: true, withRelated: POST_RELATIONS });
await obj.refresh({ require: true, withRelated: POST_RELATIONS });
obj.relations.schools = obj.relations.schools.map(row => ({ id: row.id, name: row.attributes.name, url_name: row.attributes.url_name }));

ctx.body = obj.toJSON();
Expand Down Expand Up @@ -1804,11 +1804,15 @@ export default class ApiController {
geotags = _.uniq(ctx.request.body.geotags);
}

if (type === 'short_text') {
if ('text' in ctx.request.body) {
post_object.set('text', ctx.request.body.text);
}
} else if (type === 'long_text') {
if (_.isString(ctx.request.body.text_type)) {
post_object.set('text_type', ctx.request.body.text_type);
}

if ('text' in ctx.request.body) {
post_object.set('text', ctx.request.body.text);
}

if (type === 'long_text' || type === 'story') {
if ('text' in ctx.request.body) {
post_object.set('text', ctx.request.body.text);
}
Expand Down
36 changes: 36 additions & 0 deletions src/api/db/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { break as breakGraphemes } from 'grapheme-breaker';
import { OnigRegExp } from 'oniguruma';
import Checkit from 'checkit';
import MarkdownIt from 'markdown-it';
import sanitizeHtml from 'sanitize-html';

import { uploadAttachment, downloadAttachment, generateName } from '../../utils/attachments';
import { ProfilePost as ProfilePostValidations } from './validators';
Expand Down Expand Up @@ -181,6 +182,41 @@ export function initBookshelfFromKnex(knex) {

const Post = bookshelf.Model.extend({
tableName: 'posts',
initialize() {
this.on('saving', this.ensureTextType.bind(this));
this.on('saving', this.renderHtml.bind(this));
},
ensureTextType() {
const allowedTypes = ['markdown', 'html', 'plain'];

// The default type of text is 'plain'. Plain text must be rendered using posts.text attribute.
if (!_.includes(allowedTypes, this.get('text_type'))) {
this.set('text_type', 'plain');
this.set('html', null);
}
},
renderHtml() {
const text = this.get('text');
if (!_.isString(text) || _.isEmpty(text)) {
return;
}

let html;
switch (this.get('text_type')) {
case 'markdown': {
html = postMarkdown.render(text);
break;
}
case 'html': {
// Adjust the defaults if needed.
html = sanitizeHtml(text);
break;
}
default: return;
}

this.set('html', html);
},
user() {
return this.belongsTo(User, 'user_id');
},
Expand Down
92 changes: 92 additions & 0 deletions test/integration/api/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,98 @@ describe('Post', () => {
await otherUser.destroy();
});


describe('POST /api/v1/posts', () => {
describe('story posts', () => {
after(async () => {
await knex('posts').where({ type: 'story' }).delete();
});

it('creates story post and processes text', async () => {
await expect(
{
session: sessionId,
url: `/api/v1/posts`,
method: 'POST',
body: {
type: 'story',
text: '# header',
text_type: 'markdown',
title: 'Title'
}
},
'body to satisfy',
{
text: '# header',
html: expect.it('to contain', '<h1>header</h1>')
}
);
});

it("doesn't process text if text_type is not supported", async () => {
await expect(
{
session: sessionId,
url: `/api/v1/posts`,
method: 'POST',
body: {
type: 'story',
text: '# header',
title: 'Title',
text_type: 'some unsupported type'
}
},
'body to satisfy',
{
text: '# header',
html: null,
text_type: 'plain'
}
);
});
});
});


describe('POST /api/v1/post/:id', () => {
describe('story posts', () => {
let post;
beforeEach(async () => {
post = await createPost({
user_id: user.id,
type: 'story',
text: '# initial header',
html: '<h1>initial header</h1>',
text_type: 'markdown'
});
});

afterEach(async () => {
await post.destroy();
});

it('updates post and re-renders text into html', async () => {
await expect(
{
session: sessionId,
url: `/api/v1/post/${post.id}`,
method: 'POST',
body: {
type: 'story',
text: '# new header',
text_type: 'markdown'
}
},
'body to satisfy',
{
text: '# new header',
html: expect.it('to contain', '<h1>new header</h1>')
}
);
});
});
});

describe('subscriptions', () => {
let post;

Expand Down

0 comments on commit 19730c0

Please sign in to comment.