NodeBB/NodeBB

View on GitHub
src/controllers/accounts/posts.js

Summary

Maintainability
B
6 hrs
Test Coverage
'use strict';

const db = require('../../database');
const user = require('../../user');
const posts = require('../../posts');
const topics = require('../../topics');
const categories = require('../../categories');
const pagination = require('../../pagination');
const helpers = require('../helpers');
const accountHelpers = require('./helpers');
const plugins = require('../../plugins');
const utils = require('../../utils');

const postsController = module.exports;

const templateToData = {
    'account/bookmarks': {
        type: 'posts',
        noItemsFoundKey: '[[topic:bookmarks.has_no_bookmarks]]',
        crumb: '[[user:bookmarks]]',
        getSets: function (callerUid, userData) {
            return `uid:${userData.uid}:bookmarks`;
        },
    },
    'account/posts': {
        type: 'posts',
        noItemsFoundKey: '[[user:has_no_posts]]',
        crumb: '[[global:posts]]',
        getSets: async function (callerUid, userData) {
            const cids = await categories.getCidsByPrivilege('categories:cid', callerUid, 'topics:read');
            return cids.map(c => `cid:${c}:uid:${userData.uid}:pids`);
        },
    },
    'account/upvoted': {
        type: 'posts',
        noItemsFoundKey: '[[user:has_no_upvoted_posts]]',
        crumb: '[[global:upvoted]]',
        getSets: function (callerUid, userData) {
            return `uid:${userData.uid}:upvote`;
        },
    },
    'account/downvoted': {
        type: 'posts',
        noItemsFoundKey: '[[user:has_no_downvoted_posts]]',
        crumb: '[[global:downvoted]]',
        getSets: function (callerUid, userData) {
            return `uid:${userData.uid}:downvote`;
        },
    },
    'account/best': {
        type: 'posts',
        noItemsFoundKey: '[[user:has_no_voted_posts]]',
        crumb: '[[global:best]]',
        getSets: async function (callerUid, userData) {
            const cids = await categories.getCidsByPrivilege('categories:cid', callerUid, 'topics:read');
            return cids.map(c => `cid:${c}:uid:${userData.uid}:pids:votes`);
        },
    },
    'account/watched': {
        type: 'topics',
        noItemsFoundKey: '[[user:has_no_watched_topics]]',
        crumb: '[[user:watched]]',
        getSets: function (callerUid, userData) {
            return `uid:${userData.uid}:followed_tids`;
        },
        getTopics: async function (set, req, start, stop) {
            const { sort } = req.query;
            const map = {
                votes: 'topics:votes',
                posts: 'topics:posts',
                views: 'topics:views',
                lastpost: 'topics:recent',
                firstpost: 'topics:tid',
            };

            if (!sort || !map[sort]) {
                return await topics.getTopicsFromSet(set, req.uid, start, stop);
            }
            const sortSet = map[sort];
            let tids = await db.getSortedSetRevRange(set, 0, -1);
            const scores = await db.sortedSetScores(sortSet, tids);
            tids = tids.map((tid, i) => ({ tid: tid, score: scores[i] }))
                .sort((a, b) => b.score - a.score)
                .slice(start, stop + 1)
                .map(t => t.tid);

            const topicsData = await topics.getTopics(tids, req.uid);
            topics.calculateTopicIndices(topicsData, start);
            return { topics: topicsData, nextStart: stop + 1 };
        },
    },
    'account/ignored': {
        type: 'topics',
        noItemsFoundKey: '[[user:has_no_ignored_topics]]',
        crumb: '[[user:ignored]]',
        getSets: function (callerUid, userData) {
            return `uid:${userData.uid}:ignored_tids`;
        },
    },
    'account/topics': {
        type: 'topics',
        noItemsFoundKey: '[[user:has_no_topics]]',
        crumb: '[[global:topics]]',
        getSets: async function (callerUid, userData) {
            const cids = await categories.getCidsByPrivilege('categories:cid', callerUid, 'topics:read');
            return cids.map(c => `cid:${c}:uid:${userData.uid}:tids`);
        },
    },
};

postsController.getBookmarks = async function (req, res, next) {
    await getPostsFromUserSet('account/bookmarks', req, res, next);
};

postsController.getPosts = async function (req, res, next) {
    await getPostsFromUserSet('account/posts', req, res, next);
};

postsController.getUpVotedPosts = async function (req, res, next) {
    await getPostsFromUserSet('account/upvoted', req, res, next);
};

postsController.getDownVotedPosts = async function (req, res, next) {
    await getPostsFromUserSet('account/downvoted', req, res, next);
};

postsController.getBestPosts = async function (req, res, next) {
    await getPostsFromUserSet('account/best', req, res, next);
};

postsController.getWatchedTopics = async function (req, res, next) {
    await getPostsFromUserSet('account/watched', req, res, next);
};

postsController.getIgnoredTopics = async function (req, res, next) {
    await getPostsFromUserSet('account/ignored', req, res, next);
};

postsController.getTopics = async function (req, res, next) {
    await getPostsFromUserSet('account/topics', req, res, next);
};

async function getPostsFromUserSet(template, req, res, callback) {
    const data = templateToData[template];
    const page = Math.max(1, parseInt(req.query.page, 10) || 1);

    const [userData, settings] = await Promise.all([
        accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, req.query),
        user.getSettings(req.uid),
    ]);

    if (!userData) {
        return callback();
    }
    const itemsPerPage = data.type === 'topics' ? settings.topicsPerPage : settings.postsPerPage;
    const start = (page - 1) * itemsPerPage;
    const stop = start + itemsPerPage - 1;
    const sets = await data.getSets(req.uid, userData);
    let result;
    if (plugins.hooks.hasListeners('filter:account.getPostsFromUserSet')) {
        result = await plugins.hooks.fire('filter:account.getPostsFromUserSet', {
            req: req,
            template: template,
            userData: userData,
            settings: settings,
            data: data,
            start: start,
            stop: stop,
            itemCount: 0,
            itemData: [],
        });
    } else {
        result = await utils.promiseParallel({
            itemCount: settings.usePagination ? db.sortedSetsCardSum(sets) : 0,
            itemData: getItemData(sets, data, req, start, stop),
        });
    }
    const { itemCount, itemData } = result;
    userData[data.type] = itemData[data.type];
    userData.nextStart = itemData.nextStart;

    const pageCount = Math.ceil(itemCount / itemsPerPage);
    userData.pagination = pagination.create(page, pageCount, req.query);

    userData.noItemsFoundKey = data.noItemsFoundKey;
    userData.title = `[[pages:${template}, ${userData.username}]]`;
    userData.breadcrumbs = helpers.buildBreadcrumbs([{ text: userData.username, url: `/user/${userData.userslug}` }, { text: data.crumb }]);
    userData.showSort = template === 'account/watched';
    const baseUrl = (req.baseUrl + req.path.replace(/^\/api/, ''));
    userData.sortOptions = [
        { url: `${baseUrl}?sort=votes`, name: '[[global:votes]]' },
        { url: `${baseUrl}?sort=posts`, name: '[[global:posts]]' },
        { url: `${baseUrl}?sort=views`, name: '[[global:views]]' },
        { url: `${baseUrl}?sort=lastpost`, name: '[[global:lastpost]]' },
        { url: `${baseUrl}?sort=firstpost`, name: '[[global:firstpost]]' },
    ];
    userData.sortOptions.forEach((option) => {
        option.selected = option.url.includes(`sort=${req.query.sort}`);
    });

    res.render(template, userData);
}

async function getItemData(sets, data, req, start, stop) {
    if (data.getTopics) {
        return await data.getTopics(sets, req, start, stop);
    }
    const method = data.type === 'topics' ? topics.getTopicsFromSet : posts.getPostSummariesFromSet;
    return await method(sets, req.uid, start, stop);
}