NodeBB/NodeBB

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

Summary

Maintainability
A
35 mins
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 privileges = require('../../privileges');
const pagination = require('../../pagination');
const helpers = 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-best-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`);
        },
        getTopics: async (sets, req, start, stop) => {
            let pids = await db.getSortedSetRevRangeByScore(sets, start, stop - start + 1, '+inf', 1);
            pids = await privileges.posts.filter('topics:read', pids, req.uid);
            const postObjs = await posts.getPostSummaryByPids(pids, req.uid, { stripTags: false });
            return { posts: postObjs, nextStart: stop + 1 };
        },
        getItemCount: async (sets) => {
            const counts = await Promise.all(sets.map(set => db.sortedSetCount(set, 1, '+inf')));
            return counts.reduce((acc, val) => acc + val, 0);
        },
    },
    'account/controversial': {
        type: 'posts',
        noItemsFoundKey: '[[user:has-no-controversial-posts]]',
        crumb: '[[global:controversial]]',
        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`);
        },
        getTopics: async (sets, req, start, stop) => {
            let pids = await db.getSortedSetRangeByScore(sets, start, stop - start + 1, '-inf', -1);
            pids = await privileges.posts.filter('topics:read', pids, req.uid);
            const postObjs = await posts.getPostSummaryByPids(pids, req.uid, { stripTags: false });
            return { posts: postObjs, nextStart: stop + 1 };
        },
        getItemCount: async (sets) => {
            const counts = await Promise.all(sets.map(set => db.sortedSetCount(set, '-inf', -1)));
            return counts.reduce((acc, val) => acc + val, 0);
        },
    },
    '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.getControversialPosts = async function (req, res, next) {
    await getPostsFromUserSet('account/controversial', 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) {
    const data = templateToData[template];
    const page = Math.max(1, parseInt(req.query.page, 10) || 1);

    const [{ username, userslug }, settings] = await Promise.all([
        user.getUserFields(res.locals.uid, ['username', 'userslug']),
        user.getSettings(req.uid),
    ]);

    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, { uid: res.locals.uid, username, userslug });
    let result;
    if (plugins.hooks.hasListeners('filter:account.getPostsFromUserSet')) {
        result = await plugins.hooks.fire('filter:account.getPostsFromUserSet', {
            req: req,
            template: template,
            userData: { uid: res.locals.uid, username, userslug },
            settings: settings,
            data: data,
            start: start,
            stop: stop,
            itemCount: 0,
            itemData: [],
        });
    } else {
        result = await utils.promiseParallel({
            itemCount: getItemCount(sets, data, settings),
            itemData: getItemData(sets, data, req, start, stop),
        });
    }
    const { itemCount, itemData } = result;

    const payload = {};
    payload[data.type] = itemData[data.type];
    payload.nextStart = itemData.nextStart;

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

    payload.noItemsFoundKey = data.noItemsFoundKey;
    payload.title = `[[pages:${template}, ${username}]]`;
    payload.breadcrumbs = helpers.buildBreadcrumbs([{ text: username, url: `/user/${userslug}` }, { text: data.crumb }]);
    payload.showSort = template === 'account/watched';
    const baseUrl = (req.baseUrl + req.path.replace(/^\/api/, ''));
    payload.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]]' },
    ];
    payload.sortOptions.forEach((option) => {
        option.selected = option.url.includes(`sort=${req.query.sort}`);
    });

    res.render(template, payload);
}

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

async function getItemCount(sets, data, settings) {
    if (!settings.usePagination) {
        return 0;
    }
    if (data.getItemCount) {
        return await data.getItemCount(sets);
    }
    return await db.sortedSetsCardSum(sets);
}