var RDB = require('./redis.js') schema = require('./schema.js'), posts = require('./posts.js'), utils = require('./../public/src/utils.js'), user = require('./user.js'), categories = require('./categories.js'), posts = require('./posts.js'), marked = require('marked'), threadTools = require('./threadTools.js'), postTools = require('./postTools'), async = require('async'), feed = require('./feed.js'), favourites = require('./favourites.js'), reds = require('reds'), topicSearch = reds.createSearch('nodebbtopicsearch'); marked.setOptions({ breaks: true }); (function(Topics) { Topics.minimumTitleLength = 3; Topics.getTopicData = function(tid, callback) { RDB.hgetall('topic:' + tid, function(err, data) { if(err === null) callback(data); else console.log(err); }); } Topics.getTopicDataWithUsername = function(tid, callback) { Topics.getTopicData(tid, function(topic) { user.getUserField(topic.uid, 'username', function(username) { topic.username = username; callback(topic); }); }); } Topics.getTopicPosts = function(tid, start, end, current_user, callback) { posts.getPostsByTid(tid, start, end, function(postData) { if(Array.isArray(postData) && !postData.length) return callback([]); function getFavouritesData(next) { var pids = []; for(var i=0; i 0) { Topics.hasReadTopic(tid, uid, function(read) { next(null, read); }); } else { next(null, null); } } function getTeaser(next) { Topics.getTeaser(tid, function(err, teaser) { if (err) teaser = {}; next(null, teaser); }); } async.parallel([getTopicData, getReadStatus, getTeaser], function(err, results) { if (err) { throw new Error(err); } var topicData = results[0], hasRead = results[1], teaser = results[2]; topicData.relativeTime = utils.relativeTime(topicData.timestamp); topicData.badgeclass = hasRead ? '' : 'badge-important'; topicData.teaser_text = teaser.text || ''; topicData.teaser_username = teaser.username || ''; topicData.teaser_timestamp = teaser.timestamp ? utils.relativeTime(teaser.timestamp) : ''; topicData.teaser_userpicture = teaser.picture; callback(topicData); }); } Topics.getAllTopics = function(limit, after, callback) { RDB.smembers('topics:tid', function(err, tids) { var topics = [], numTids, x; // Sort into ascending order tids.sort(function(a, b) { return a - b; }); // Eliminate everything after the "after" tid if (after) { for(x=0,numTids=tids.length;x= after) { tids = tids.slice(0, x); break; } } } if (limit) { if (limit > 0 && limit < tids.length) { tids = tids.slice(tids.length - limit); } } // Sort into descending order tids.sort(function(a, b) { return b - a; }); async.each(tids, function(tid, next) { Topics.getTopicDataWithUsername(tid, function(topicData) { topics.push(topicData); next(); }); }, function(err) { callback(topics); }); }); } Topics.markAllRead = function(uid, callback) { RDB.smembers('topics:tid', function(err, tids) { if(err) { console.log(err); callback(err, null); return; } if(tids && tids.length) { for(var i=0; i.+\n\n/, ''); stripped = utils.strip_tags(postTools.markdownToHTML(stripped)); } callback(null, { "text": stripped, "username": userData.username, "picture": userData.picture, "timestamp" : timestamp }); }); }); } else callback(new Error('no-teaser-found')); }); } Topics.emitTitleTooShortAlert = function(socket) { socket.emit('event:alert', { type: 'error', timeout: 2000, title: 'Title too short', message: "Please enter a longer title. At least " + Topics.minimumTitleLength + " characters.", alert_id: 'post_error' }); } Topics.post = function(uid, title, content, category_id, images, callback) { if (!category_id) throw new Error('Attempted to post without a category_id'); if(content) content = content.trim(); if(title) title = title.trim(); if (uid === 0) { callback(new Error('not-logged-in'), null); return; } else if(!title || title.length < Topics.minimumTitleLength) { callback(new Error('title-too-short'), null); return; } else if (!content || content.length < posts.miminumPostLength) { callback(new Error('content-too-short'), null); return; } user.getUserField(uid, 'lastposttime', function(lastposttime) { if(Date.now() - lastposttime < config.post_delay) { callback(new Error('too-many-posts'), null); return; } RDB.incr(schema.global().next_topic_id, function(err, tid) { RDB.handle(err); // Global Topics if (uid == null) uid = 0; if (uid !== null) { RDB.sadd('topics:tid', tid); } else { // need to add some unique key sent by client so we can update this with the real uid later RDB.lpush(schema.topics().queued_tids, tid); } var slug = tid + '/' + utils.slugify(title); var timestamp = Date.now(); RDB.hmset('topic:' + tid, { 'tid': tid, 'uid': uid, 'cid': category_id, 'title': title, 'slug': slug, 'timestamp': timestamp, 'lastposttime': 0, 'postcount': 0, 'locked': 0, 'deleted': 0, 'pinned': 0 }); topicSearch.index(title, tid); RDB.set('topicslug:' + slug + ':tid', tid); user.addTopicIdToUser(uid, tid); // let everyone know that there is an unread topic in this category RDB.del('cid:' + category_id + ':read_by_uid', function(err, data) { Topics.markAsRead(tid, uid); }); // in future it may be possible to add topics to several categories, so leaving the door open here. RDB.zadd('categories:' + category_id + ':tid', timestamp, tid); RDB.hincrby('category:' + category_id, 'topic_count', 1); RDB.incr('totaltopiccount'); feed.updateCategory(category_id); posts.create(uid, tid, content, images, function(postData) { if (postData) { RDB.lpush(schema.topics(tid).posts, postData.pid); // Auto-subscribe the post creator to the newly created topic threadTools.toggleFollow(tid, uid); // Notify any users looking at the category that a new topic has arrived Topics.getTopicForCategoryView(tid, uid, function(topicData) { io.sockets.in('category_' + category_id).emit('event:new_topic', topicData); io.sockets.in('recent_posts').emit('event:new_topic', topicData); }); callback(null, postData); } }); }); }); }; Topics.getTopicField = function(tid, field, callback) { RDB.hget('topic:' + tid, field, callback); } Topics.getTopicFields = function(tid, fields, callback) { RDB.hmgetObject('topic:' + tid, fields, function(err, data) { if(err === null) { callback(data); } else { console.log(err); } }); } Topics.setTopicField = function(tid, field, value) { RDB.hset('topic:' + tid, field, value); } Topics.increasePostCount = function(tid) { RDB.hincrby('topic:' + tid, 'postcount', 1); } Topics.isLocked = function(tid, callback) { Topics.getTopicField(tid, 'locked', function(err, locked) { callback(locked); }); } Topics.updateTimestamp = function(tid, timestamp) { RDB.zadd(schema.topics().recent, timestamp, tid); Topics.setTopicField(tid, 'lastposttime', timestamp); } Topics.addPostToTopic = function(tid, pid) { RDB.rpush('tid:' + tid + ':posts', pid); } Topics.getPids = function(tid, callback) { RDB.lrange('tid:' + tid + ':posts', 0, -1, callback); } Topics.reIndexTopic = function(tid, callback) { Topics.getPids(tid, function(err, pids) { if(err) { callback(err); } else { posts.reIndexPids(pids, function(err) { if(err) { callback(err); } else { callback(null); } }); } }); } Topics.reIndexAll = function(callback) { RDB.smembers('topics:tid', function(err, tids) { if(err) { callback(err, null); } else { async.each(tids, Topics.reIndexTopic, function(err) { if(err) { callback(err, null); } else { callback(null, 'All topics reindexed.'); } }); } }); } }(exports));