diff --git a/lib/boot/boot.js b/lib/boot/boot.js index 5f69429f03..6064024642 100644 --- a/lib/boot/boot.js +++ b/lib/boot/boot.js @@ -32,10 +32,10 @@ import '../logout/logout.js'; import '../auth-facebook/auth-facebook'; import '../admin/admin.js'; import '../settings/settings.js'; -import '../forum/forum.js'; -import '../topic/topic.js'; import '../homepage/homepage.js'; import '../newsfeed/newsfeed.js'; +import '../forum/forum.js'; +import '../topic/topic.js'; // require('proposal'); // require('404'); diff --git a/lib/boot/index.js b/lib/boot/index.js index 8015788055..1576d914b7 100644 --- a/lib/boot/index.js +++ b/lib/boot/index.js @@ -92,12 +92,6 @@ app.use('/settings', require('lib/settings-api')); app.use('/forgot', require('lib/forgot-api')); -/* - * Forums routes - */ - -app.get('/forums/new', require('lib/forum')); - /* * Stats routes */ @@ -201,4 +195,5 @@ app.use(require('lib/forgot')); app.use(require('lib/help')); app.use(require('lib/homepage')); app.use(require('lib/topic')); +app.use(require('lib/forum')); app.use(require('lib/404')); diff --git a/lib/config/index.js b/lib/config/index.js index 2944c8ac7e..466fef4b23 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -36,7 +36,7 @@ var defaultsPath = resolve(configPath, 'defaults.json'); var envPath = resolve(configPath, environment + '.json'); var defaultConfig = require(defaultsPath); -var localConfig = fs.existsSync(envPath) && require(envPath) || {}; +var localConfig = fs.existsSync(envPath) && require(envPath) || {}; var config = {}; forEach(defaultConfig, parse); diff --git a/lib/db-api/forum.js b/lib/db-api/forum.js index 4da84add5a..0957461900 100644 --- a/lib/db-api/forum.js +++ b/lib/db-api/forum.js @@ -4,34 +4,6 @@ var Log = require('debug'); var log = new Log('democracyos:db-api:forum'); var Forum = models.Forum; -exports.all = function all(fn) { - log('Looking for all forums'); - - Forum - .find({ deletedAt: null }) - .populate('owner') - .select('id title summary url owner imageUrl') - .exec(function(err, forums) { - if (err) { - log('Found error: %s', err); - return fn(err); - } - - log('Delivering forums %j', forums); - - Forum.findOne({ deletedAt: null }).exec(function(_err) { - if (_err) { - log('Found error: %s', _err); - return fn(_err); - } - - fn(null, { forums: forums }); - }); - }); - - return this; -}; - exports.create = function create(data, fn) { log('Creating new forum'); @@ -49,28 +21,16 @@ exports.create = function create(data, fn) { } }; -exports.findByOwner = function findByOwner(owner, fn) { - log('Searching for forums whose owner is %s', owner); - - Forum.find({ owner: owner }) - .exec(function(err, forums) { - if (err) { - log('Found error: %j', err); - return fn(err); - } - - log('Found %d forums', forums.length); - fn(null, forums); - }); - - return this; +exports.del = function del(forum, cb) { + log('Deleting forum'); + forum.softdelete(cb); }; exports.findOneByOwner = function findOneByOwner(owner, fn) { log('Searching forum of owner %s', owner); Forum - .where({ owner: owner }) + .where({ owner: owner, deletedAt: null }) .findOne(function(err, forum) { if (err) { log('Found error: %j', err); @@ -89,7 +49,9 @@ exports.findOneByOwner = function findOneByOwner(owner, fn) { exports.findById = function findById(id, fn) { log('Searching for forum with id %s', id); - Forum.findById(id, function(err, forum) { + Forum + .where({ deletedAt: null, id: id }) + .findOne(id, function(err, forum) { if (err) { log('Found error: %j', err); return fn(err); @@ -109,8 +71,8 @@ exports.findOneByName = function findOneByName(name, fn) { log('Searching for forum with name %s', name); Forum - .where({ name: name }) - .findOne(function(err, forum) { + .where({ deletedAt: null, name: name }) + .findOne(function(err, forum) { if (err) { log('Found error: %j', err); return fn(err); @@ -126,14 +88,13 @@ exports.findOneByName = function findOneByName(name, fn) { return this; }; -exports.count = function find(query, fn) { - Forum.count(query).exec(fn); -}; - exports.exists = function exists(name, fn) { name = normalize(name); - Forum.findOne({ name: name }, function(err, forum) { - return err ? fn(err) : fn(null, !!forum); + Forum + .find({ deletedAt: null, name: name }) + .limit(1) + .exec(function(err, forum) { + return fn(err, !!(forum && forum.length)); }); }; diff --git a/lib/forum-api/index.js b/lib/forum-api/index.js index 9aa7dd3a66..db30de68f6 100644 --- a/lib/forum-api/index.js +++ b/lib/forum-api/index.js @@ -10,6 +10,8 @@ var log = new Log('democracyos:forum'); var app = module.exports = express(); app.get('/forum/mine', restrict, function(req, res) { + if (!req.isAuthenticated()) return res.status(404).send(); + api.forum.findOneByOwner(req.user.id, function(err, forum) { if (err) return _handleError(err, req, res); @@ -54,14 +56,18 @@ app.post('/forum', restrict, maintenance, function(req, res) { }); }); -app.delete('/forum/:id', restrict, maintenance, function(req, res) { - api.forum.findById(req.params.id, function(err, forum) { +app.del('/forum/:name', restrict, maintenance, function(req, res) { + api.forum.findOneByName(req.params.name, function(err, forum) { if (err) return _handleError(err, req, res); - if (!forum) return _handleError('The user doesnt have any forum.', req, res); + if (!forum) return _handleError('Forum not found.', req, res); + + if (forum.owner.toString() !== req.user._id.toString()) { + return res.status(401).send(); + } - log('Trying to delete forum: %o', forum); + log('Trying to delete forum: %s', forum.id); - forum.remove(function(_err) { + api.forum.del(forum, function(_err) { if (_err) return res.status(500).json(_err); res.status(200).send(); }); diff --git a/lib/forum/view.js b/lib/forum-form/forum-form.js similarity index 100% rename from lib/forum/view.js rename to lib/forum-form/forum-form.js diff --git a/lib/forum/styles.styl b/lib/forum-form/styles.styl similarity index 98% rename from lib/forum/styles.styl rename to lib/forum-form/styles.styl index 3ef3ab0843..2c4e8b5a0c 100644 --- a/lib/forum/styles.styl +++ b/lib/forum-form/styles.styl @@ -30,10 +30,6 @@ line-height 1.5 margin 15px 0 - -body.forum-new - background #ccc - .forum-new-form fieldset margin-top 1px diff --git a/lib/forum/template.jade b/lib/forum-form/template.jade similarity index 100% rename from lib/forum/template.jade rename to lib/forum-form/template.jade diff --git a/lib/forum-middlewares/forum-middlewares.js b/lib/forum-middlewares/forum-middlewares.js new file mode 100644 index 0000000000..dd685043a1 --- /dev/null +++ b/lib/forum-middlewares/forum-middlewares.js @@ -0,0 +1,57 @@ +import debug from 'debug'; +import page from 'page'; +import forumStore from '../forum-store/forum-store'; + +const log = debug('democracyos:forum-middlewares'); + +/** + * Load from ':forum' param, and set ctx.forum. + */ + +export function getForum (ctx, next) { + if (!ctx.params.forum) return next(); + + forumStore.get(ctx.params.forum) + .then(forum => { + ctx.forum = forum; + log(`setted ctx.forum with '${forum.name}'.`); + next(); + }) + .catch(err => { + if (404 === err.status) { + log(`forum not found '${ctx.params.forum}'.`); + return next(); + } + log('Found error %s', err); + }); +} + +/** + * Load of logged in user, and set ctx.userForum. + */ + +export function getUserForum (ctx, next) { + forumStore.getUserForum() + .then(userForum => { + ctx.userForum = userForum; + log(`setted ctx.userForum with '${userForum.name}'.`); + next(); + }) + .catch(err => { + if (404 === err.status) return next(); + log('Found error %s', err); + }); +} + +/** + * Dont let in users that already have a forum. + */ + +export function restrictUserWithForum (ctx, next) { + forumStore.getUserForum() + .then(() => page('/')) + .catch(err => { + if (404 === err.status) return next(); + log('Found error %s', err); + }); +} diff --git a/lib/forum-router/create-router.js b/lib/forum-router/create-router.js new file mode 100644 index 0000000000..4174f2fc09 --- /dev/null +++ b/lib/forum-router/create-router.js @@ -0,0 +1,11 @@ +var prefix = '/:forum'; + +function createRouter(config) { + var singleForum = config.singleForum; + return function forumRouter(route) { + if (singleForum) return route; + return prefix + ('/' === route ? '' : route); + }; +} + +module.exports = createRouter; diff --git a/lib/forum-router/forum-router.js b/lib/forum-router/forum-router.js new file mode 100644 index 0000000000..d6196e6ad6 --- /dev/null +++ b/lib/forum-router/forum-router.js @@ -0,0 +1,4 @@ +import config from '../config/config.js'; +import createRouter from './create-router'; + +export default createRouter(config); diff --git a/lib/forum-router/index.js b/lib/forum-router/index.js new file mode 100644 index 0000000000..c6ba9a0de6 --- /dev/null +++ b/lib/forum-router/index.js @@ -0,0 +1,4 @@ +var config = require('lib/config'); +var createRouter = require('./create-router'); + +module.exports = createRouter(config); diff --git a/lib/forum-store/forum-store.js b/lib/forum-store/forum-store.js index 035ee5bc90..1d78072434 100644 --- a/lib/forum-store/forum-store.js +++ b/lib/forum-store/forum-store.js @@ -1,14 +1,11 @@ import bus from 'bus'; import Store from '../store/store'; -import Log from 'debug'; - -const log = new Log('democracyos:forum-store'); class ForumStore extends Store { constructor () { super(); - this.getFromParamsMiddleware = this.getFromParamsMiddleware.bind(this); + this._userForumName = null; bus.on('logout', this.unloadUserForum.bind(this)); } @@ -26,40 +23,34 @@ class ForumStore extends Store { return forum; } - /** - * Get the Forum of the current user - * - * @return {Promise} fetch - * @api public - */ getUserForum () { - return this.get('mine'); + let name = this._userForumName; + if (name && this.items.get(name)) return Promise.resolve(this.items.get(name)); + + if (this._fetches.get('mine')) return this._fetches.get('mine'); + + let fetch = this._fetch('mine') + .then(item => { + this._userForumName = item.name; + this.items.set(item.name, item); + bus.emit(`${this.name()}:update:${item.name}`, item); + }) + .catch(err => { + this.log('Found error', err); + }); + + return fetch; } unloadUserForum () { - return this.unload('mine'); + return this.unload(this._userForumName); } destroyUserForum () { - return this.destroy('mine'); + return this.destroy(this._userForumName); } - /** - * Middleware to load forum from current page url, gets it from '/:forum'. - * - * @return {Middleware} - * @api public - */ - getFromParamsMiddleware (ctx, next) { - const name = ctx.params.forum; - - this.get(name) - .then(forum => { - ctx.forum = forum; - next(); - }) - .catch(err => log('Found error %s', err)); - } + } const forumStore = new ForumStore(); diff --git a/lib/forum-view/styles.styl b/lib/forum-view/styles.styl deleted file mode 100644 index ad8587d912..0000000000 --- a/lib/forum-view/styles.styl +++ /dev/null @@ -1,49 +0,0 @@ -a.new - margin-left 20px - -.forum-item - &:hover - cursor pointer - - .thumbnail - &:hover - background-color #fcf8e3 - - height 400px - .caption - .category - position absolute - top 10px - right 0 - background-color black - color white - text-transform uppercase - font-weight bold - margin-right 20px - padding-right 20px - padding-left 10px - font-size 0.8em - - h3.title - margin-top 0 - - .url - position absolute - bottom 40px - text-align center - - a - padding-left 0 - - h4.list-group-item-heading - margin-top 5px - -.crop -// width 200px - height 150px - overflow hidden - -.crop img -// width 400px - height 300px - margin -75px 0 0 0px \ No newline at end of file diff --git a/lib/forum-view/template.jade b/lib/forum-view/template.jade deleted file mode 100644 index 17d39fc840..0000000000 --- a/lib/forum-view/template.jade +++ /dev/null @@ -1,15 +0,0 @@ -div(class='col-sm-#{6*visibility} col-md-#{4*visibility} forum-item', data-id='#{id}') - .thumbnail - .crop - img(src='#{image}') - .caption - .category #{category} - h3.title - span #{title} - .badges.hide - a(href='#') - span.glyphicon.glyphicon-comment - span.badge #{participants} - p.summary #{summary} - .url - a.btn.btn-sm #{fancyUrl} \ No newline at end of file diff --git a/lib/forum-view/view.js b/lib/forum-view/view.js deleted file mode 100644 index 98035d7dbf..0000000000 --- a/lib/forum-view/view.js +++ /dev/null @@ -1,22 +0,0 @@ -import template from './template.jade'; -import page from 'page'; -import View from '../view/view'; - -export default class ForumView extends View { - - constructor (locals) { - super(template, locals); - this.url = locals.url; - } - - switchOn () { - this.el.on('click', this.bound('onclick')); - } - - onclick () { - if (this.url) - window.location = this.url; - else - page('/signin'); - } -} diff --git a/lib/forum/forum.js b/lib/forum/forum.js index d26e632b69..79e47413ec 100644 --- a/lib/forum/forum.js +++ b/lib/forum/forum.js @@ -1,21 +1,39 @@ +import debug from 'debug'; import page from 'page'; +import config from '../config/config'; +import forumRouter from '../forum-router/forum-router'; +import { getForum, restrictUserWithForum } from '../forum-middlewares/forum-middlewares'; import o from 'component-dom'; -import user from '../user/user'; -import View from './view'; import title from '../title/title'; import t from 't-component'; -import forumStore from '../forum-store/forum-store'; +import user from '../user/user'; +import ForumForm from '../forum-form/forum-form'; +import topics from '../topics/topics'; +import topicsFilter from '../topics-filter/topics-filter'; + +const log = debug('democracyos:forum'); + +if (!config.singleForum) { + page(forumRouter('/'), user.optional, getForum, topics.middleware, topicsFilter.middleware, (ctx, next) => { + let forum = ctx.forum; + let topic = topicsFilter.items()[0]; + + title(forum.title); + o('body').addClass('browser-page'); + + ctx.path = `${forum.url}/topic/${topic.id}`; + + log(`rendering landing of '${forum.name}'.`); -page('/forums/new', user.required, () => { - title(t('forum.new.title')); - o('body').addClass('forum-new'); + next(); + }); - var section = o('section.site-content').empty(); - var view = new View(); - view.appendTo(section[0]); -}); + page('/forums/new', user.required, restrictUserWithForum, () => { + title(t('forum.new.title')); + o('body').addClass('forum-new'); -page('/:forum', forumStore.getFromParamsMiddleware, ctx => { - console.log(ctx.params.forum); - console.log(ctx.forum); -}); + let section = o('section.site-content').empty(); + let view = new ForumForm(); + view.appendTo(section[0]); + }); +} diff --git a/lib/forum/index.js b/lib/forum/index.js index 7f7136a626..1a57f430d2 100644 --- a/lib/forum/index.js +++ b/lib/forum/index.js @@ -1,12 +1,16 @@ -/** - * Module dependencies. - */ - var express = require('express'); +var forumRouter = require('lib/forum-router'); + var app = module.exports = express(); /** * GET Add democracy form */ -app.use('/forums/new', require('lib/layout')); +app.get('/forums/new', require('lib/layout')); + +/** + * GET Forum Show + */ + +app.get(forumRouter('/'), require('lib/layout')); diff --git a/lib/homepage/homepage.js b/lib/homepage/homepage.js index a4681ea613..12ad1c9c09 100644 --- a/lib/homepage/homepage.js +++ b/lib/homepage/homepage.js @@ -9,11 +9,9 @@ import noTopics from './no-topics.jade'; import createFirstTopic from './create-first-topic.jade'; import visibility from '../visibility/visibility'; import config from '../config/config'; -import Router from '../router'; +import forumRouter from '../forum-router/forum-router'; -// Routing. -let log = debug('democracyos:homepage'); -let router = Router(config); +const log = debug('democracyos:homepage'); page('/', multiDemocracy); page('/', user.optional, visibility, sidebarready, singleForum); @@ -26,6 +24,7 @@ function multiDemocracy(ctx, next) { if (config.singleForum) return next(); ctx.path = '/newsfeed'; + next(); } @@ -44,11 +43,14 @@ function singleForum(ctx, next) { content.append(dom(createFirstTopic)); } }); - bus.once('page:change', () => pageChanged = true && body.removeClass('browser-page')); + bus.once('page:change', () => { + pageChanged = true; + body.removeClass('browser-page'); + }); return bus.emit('page:render'); } log(`render topic ${topic.id}`); - ctx.path = router(`/topic/${topic.id}`); + ctx.path = forumRouter(`/topic/${topic.id}`); next(); } diff --git a/lib/models/forum.js b/lib/models/forum.js index c2045da47d..96b2093dfc 100644 --- a/lib/models/forum.js +++ b/lib/models/forum.js @@ -37,8 +37,14 @@ var ForumSchema = new Schema({ deletedAt: { type: Date } }); -ForumSchema.index({ owner: -1 }); -ForumSchema.index({ name: -1 }); +ForumSchema.index({ id: -1, deletedAt: 1 }); +ForumSchema.index({ owner: -1, deletedAt: 1 }); +ForumSchema.index({ name: -1, deletedAt: 1 }); + +ForumSchema.methods.softdelete = function(cb) { + this.deletedAt = new Date(); + return this.save(cb); +}; /** * Make Schema `.toObject()` and diff --git a/lib/newsfeed/newsfeed.js b/lib/newsfeed/newsfeed.js index dbacedc823..55291a4f45 100644 --- a/lib/newsfeed/newsfeed.js +++ b/lib/newsfeed/newsfeed.js @@ -1,11 +1,14 @@ import o from 'component-dom'; import page from 'page'; +import config from '../config/config'; import user from '../user/user'; import Newsfeed from './view'; -page('/newsfeed', user.optional, () => { - let newsfeed = new Newsfeed(); +if (!config.singleForum) { + page('/newsfeed', user.optional, () => { + let newsfeed = new Newsfeed(); - o(document.body).addClass('newsfeed'); - newsfeed.replace('#content'); -}); + o(document.body).addClass('newsfeed'); + newsfeed.replace('#content'); + }); +} diff --git a/lib/router/index.js b/lib/router/index.js deleted file mode 100644 index f88afb7321..0000000000 --- a/lib/router/index.js +++ /dev/null @@ -1,8 +0,0 @@ - -function Router(config) { - return function router(route) { - return (config.singleForum ? '' : '/:forum') + route; - } -} - -module.exports = Router; diff --git a/lib/settings-forum/delete-modal-view.js b/lib/settings-forum/delete-modal-view.js index 07492b1e08..981feba18f 100644 --- a/lib/settings-forum/delete-modal-view.js +++ b/lib/settings-forum/delete-modal-view.js @@ -1,6 +1,7 @@ import modal from 'nanomodal'; import FormView from '../form-view/form-view'; import template from './delete-modal.jade'; +import forumStore from '../forum-store/forum-store'; export default class DeleteForumModal extends FormView { diff --git a/lib/settings-forum/delete-modal.jade b/lib/settings-forum/delete-modal.jade index 1f5d02e00c..692e5df0b9 100644 --- a/lib/settings-forum/delete-modal.jade +++ b/lib/settings-forum/delete-modal.jade @@ -1,5 +1,5 @@ .content#forum-delete-modal - form(method='delete', action='/api/forum/'+forum.name, autosubmit, autovalidate) + form(method='delete', autovalidate) .title=t('settings-forums.delete.confirmation.title') a(href='#').close.cancel | × diff --git a/lib/settings-forum/forum-row-view.js b/lib/settings-forum/forum-row-view.js index 692f804bb6..430ef19cca 100644 --- a/lib/settings-forum/forum-row-view.js +++ b/lib/settings-forum/forum-row-view.js @@ -1,6 +1,6 @@ import View from '../view/view.js'; import template from './forum-row.jade'; -// import DeleteForumModal from './delete-modal-view.js'; +import DeleteForumModal from './delete-modal-view.js'; export default class ForumRowView extends View { @@ -22,7 +22,6 @@ export default class ForumRowView extends View { } remove () { - console.log('Delete not implemented, see https://github.com/DemocracyOS/app/issues/946.'); - // return new DeleteForumModal(this.forum); + return new DeleteForumModal(this.forum); } } diff --git a/lib/settings-forum/view.js b/lib/settings-forum/view.js index 3b5b45e809..968327c53a 100644 --- a/lib/settings-forum/view.js +++ b/lib/settings-forum/view.js @@ -1,5 +1,4 @@ import render from '../render/render'; -import bus from 'bus'; import View from '../view/view'; import ForumRow from './forum-row-view'; import template from './template.jade'; diff --git a/lib/store/memory-cache.js b/lib/store/memory-cache.js new file mode 100644 index 0000000000..7608ab52a5 --- /dev/null +++ b/lib/store/memory-cache.js @@ -0,0 +1,21 @@ +export default class MemoryCache { + constructor () { + this.items = {}; + } + + get (id) { + return this.items[id]; + } + + set (id, item) { + this.items[id] = item; + return item; + } + + remove (id) { + let item = this.items[id]; + if (!item) return null; + delete this.items[id]; + return item; + } +} diff --git a/lib/store/store.js b/lib/store/store.js index 6cfa3d3e3e..981c2a4b6f 100644 --- a/lib/store/store.js +++ b/lib/store/store.js @@ -1,13 +1,18 @@ import bus from 'bus'; +import debug from 'debug'; +import MemoryCache from './memory-cache'; import request from '../request/request'; export default class Store { constructor () { - this._fetches = {}; - this._requests = {}; - this._destroys = {}; - this.items = {}; + this._fetches = new MemoryCache(); + this._fetchRequests = new MemoryCache(); + this._destroys = new MemoryCache(); + this.items = new MemoryCache(); + this.allItems = null; + + this.log = debug(`democracyos:${this.name()}`); } /** @@ -51,14 +56,10 @@ export default class Store { * @api public */ unload (id) { - if (!this.items[id]) return this; - - if (this._fetches[id]) this._requests[id].abort(); - - delete this.items[id]; - - bus.emit(`${this.name()}:unload:${id}`); - + if (this.items.remove(id)) { + this._fetchAbort(id); + bus.emit(`${this.name()}:unload:${id}`); + } return this; } @@ -70,29 +71,42 @@ export default class Store { * @api public */ get (id, ...options) { - if (this.items[id]) return Promise.resolve(this.items[id]); - if (this._fetches[id]) return this._fetches[id]; - - this._fetches[id] = new Promise((resolve, reject) => { - this._requests[id] = request - .get(this.url(id, ...options)) - .end((err, res) => { - delete this._requests[id]; - delete this._fetches[id]; - - if (err || !res.ok) return reject(err || res.error); - - var u = res.body; - - this.items[id] = this.parse(u); - - resolve(u); - - bus.emit(`${this.name()}:update:${id}`, u); - }); - }); + if (this.items.get(id)) return Promise.resolve(this.items.get(id)); + if (this._fetches.get(id)) return this._fetches.get(id); + + let fetch = this._fetch(id, ...options) + .then(item => { + this.items.set(id, item); + bus.emit(`${this.name()}:update:${id}`, item); + }) + .catch(err => { + this.log('Found error', err); + }); + + return fetch; + } - return this._fetches[id]; + /** + * Method to get a Model from the Database. + * + * @param {String} id + * @return {Promise} fetch + * @api public + */ + all() { + if (this.allItems) return Promise.resolve(this.allItems); + if (this._fetches.get('all')) return this._fetches.get('all'); + + let fetch = this._fetch('all', ...options) + .then(items => { + this.allItems = items; + bus.emit(`${this.name()}:all:load`, items); + }) + .catch(err => { + this.log('Found error', err); + }); + + return fetch; } /** @@ -103,28 +117,72 @@ export default class Store { * @api public */ destroy (id, ...options) { - if (!this.items[id]) { + let item = this.items.get(id); + if (!item) { return Promise.reject(new Error('Cannot delete unexistent item.')); } - if (this._destroys[id]) return this._destroys[id]; + if (this._destroys.get(id)) return this._destroys.get(id); - this._destroys[id] = new Promise((resolve, reject) => { + let destroy = new Promise((resolve, reject) => { request - .delete(this.url(id, ...options)) + .del(this.url(id, ...options)) .end((err, res) => { - delete this._destroys[id]; + this._destroys.remove(id); - if (err || !res.ok) return reject(err || res.error); + if (err || !res.ok) return reject(err); this.unload(id); - resolve(); + resolve(item); + }); + }); + + this._destroys.set(id, destroy); + + return destroy; + } - bus.emit(`${this.name()}:delete:${id}`); + /** + * Fetch an item from backend + * + * @param {String} id + * @return {Promise} fetch + * @api protected + */ + _fetch (id, ...options) { + if (this._fetches.get(id)) return this._fetches.get(id); + + let fetch = new Promise((resolve, reject) => { + let req = request + .get(this.url(id, ...options)) + .end((err, res) => { + this._fetches.remove(id); + this._fetchRequests.remove(id); + + if (err) return reject(err); + + var item = this.parse(res.body); + + resolve(item); }); + + this._fetchRequests.set(id, req); }); - return this._destroys[id]; + this._fetches.set(id, fetch); + + return fetch; + } + + /** + * Aborts a currently running fetch to the server + * + * @param {String} id + * @api protected + */ + _fetchAbort (id) { + if (!this._fetchRequests.get(id)) return; + this._fetchRequests.get(id).abort(); } } diff --git a/lib/topic-store/topic-store.js b/lib/topic-store/topic-store.js new file mode 100644 index 0000000000..11a24f137e --- /dev/null +++ b/lib/topic-store/topic-store.js @@ -0,0 +1,32 @@ +import bus from 'bus'; +import Store from '../store/store'; + +class TopicStore extends Store { + + constructor(forumName) { + super(); + this.setForum(forumName); + } + + name () { + return 'topic-store'; + } + + url(path = 'all') { + return `/api/topic/${path}?forum=${this.forumName}`; + } + + parse (topic) { + topic.url = '/' + topic.id; + return topic; + } + + setForum(forumName) { + this.forumName = forumName; + this.allItems = null; + } +} + +const topicStore = new TopicStore(); + +export default topicStore; diff --git a/lib/topic/index.js b/lib/topic/index.js index 70e8b0128d..7dbf6fc9bf 100644 --- a/lib/topic/index.js +++ b/lib/topic/index.js @@ -3,8 +3,7 @@ */ var express = require('express'); -var config = require('lib/config'); -var router = require('lib/router')(config); +var forumRouter = require('lib/forum-router'); /** * Exports Application @@ -12,4 +11,4 @@ var router = require('lib/router')(config); var app = module.exports = express(); -app.get(router('/topic/:id'), require('lib/layout')); +app.get(forumRouter('/topic/:id'), require('lib/layout')); diff --git a/lib/topic/topic.js b/lib/topic/topic.js index 5c3882537f..6090cc2e6b 100644 --- a/lib/topic/topic.js +++ b/lib/topic/topic.js @@ -12,20 +12,21 @@ import Comments from '../comments-view/view'; import sidebar from '../sidebar/main'; import filter from '../topics-filter/topics-filter'; import locker from '../browser-lock/locker'; +import { getForum } from '../forum-middlewares/forum-middlewares'; +import forumRouter from '../forum-router/forum-router.js'; -let log = debug('democracyos:topic:page'); +const log = debug('democracyos:topic:page'); -var router = require('../router')(config); - -page(router('/topic/:id'), user.optional, load, (ctx, next) => { +page(forumRouter('/topic/:id'), user.optional, getForum, load, (ctx, next) => { bus.emit('page:render', ctx.topic); + log(`rendering Topic ${ctx.params.id}`); if (!ctx.topic) { log('Topic %s not found', ctx.params.id); return next(); } - sidebar.forum(ctx.params.forum); + if (ctx.forum) sidebar.forum(ctx.forum.name); // Render sidebar list sidebar.ready(() => { @@ -43,7 +44,7 @@ page(router('/topic/:id'), user.optional, load, (ctx, next) => { // Build article's content container // and render to section.app-content - var article = new Article(ctx.topic, ctx.path); + let article = new Article(ctx.topic, ctx.path); article.appendTo('section.app-content'); // Build article's meta @@ -112,12 +113,14 @@ function load (ctx, next) { .end(function(err, res) { if (!err && res.status == 404) { ctx.topic = null; + log(`topic ${ctx.params.id} not found.`); return next(); } if (err || !res.ok) return log('Found error: %s', err || res.error); ctx.topic = res.body; + log(`loaded topic ${ctx.topic.id}.`); next(); }); } diff --git a/lib/topics-filter/topics-filter.js b/lib/topics-filter/topics-filter.js index c908ae60e1..0e863beb7f 100644 --- a/lib/topics-filter/topics-filter.js +++ b/lib/topics-filter/topics-filter.js @@ -3,7 +3,6 @@ */ import debug from 'debug'; -import t from 't-component'; import merge from 'merge'; import Stateful from '../stateful/stateful.js'; import Storage from '../storage/storage.js'; @@ -21,6 +20,7 @@ class TopicsFilter extends Stateful { this.fetch = this.fetch.bind(this); this.refresh = this.refresh.bind(this); this.ontopicsload = this.ontopicsload.bind(this); + this.middleware = this.middleware.bind(this); this.state('initializing'); this.initialize(); @@ -88,9 +88,9 @@ class TopicsFilter extends Stateful { topics.get().forEach(function(item) { if (item.id === id && !item.voted) { - item.voted = true; - item.participants.push(user.id); - view.reload(); + item.voted = true; + item.participants.push(user.id); + view.reload(); } }); }; @@ -151,7 +151,7 @@ class TopicsFilter extends Stateful { // Create param object and call recursively var obj = {}; return obj[key] = value, this.set(obj); - }; + } // key is an object merge(this.$_filters, key); @@ -161,7 +161,7 @@ class TopicsFilter extends Stateful { function onready() { this.emit('change', this.get()); - }; + } // reload items with updated filters this.reload(); @@ -262,7 +262,11 @@ class TopicsFilter extends Stateful { // .length; return this.$_counts[status]; - }; + } + + middleware (ctx, next) { + this.ready(next); + } } /**