mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-30 18:46:01 +01:00
* feat: show unread categories based on unread topics if a category has unread topics in one of its children then mark category unread deprecate cid:<cid>:read_by_uid sets upgrade script to remove the old sets * chore: up harmony
246 lines
7.0 KiB
JavaScript
246 lines
7.0 KiB
JavaScript
'use strict';
|
|
|
|
const meta = require('../meta');
|
|
const categories = require('../categories');
|
|
const topics = require('../topics');
|
|
const events = require('../events');
|
|
const user = require('../user');
|
|
const groups = require('../groups');
|
|
const privileges = require('../privileges');
|
|
|
|
const categoriesAPI = module.exports;
|
|
|
|
const hasAdminPrivilege = async (uid, privilege = 'categories') => {
|
|
const ok = await privileges.admin.can(`admin:${privilege}`, uid);
|
|
if (!ok) {
|
|
throw new Error('[[error:no-privileges]]');
|
|
}
|
|
};
|
|
|
|
categoriesAPI.list = async (caller) => {
|
|
async function getCategories() {
|
|
const cids = await categories.getCidsByPrivilege('categories:cid', caller.uid, 'find');
|
|
return await categories.getCategoriesData(cids);
|
|
}
|
|
|
|
const [isAdmin, categoriesData] = await Promise.all([
|
|
user.isAdministrator(caller.uid),
|
|
getCategories(),
|
|
]);
|
|
|
|
return {
|
|
categories: categoriesData.filter(category => category && (!category.disabled || isAdmin)),
|
|
};
|
|
};
|
|
|
|
categoriesAPI.get = async function (caller, data) {
|
|
const [userPrivileges, category] = await Promise.all([
|
|
privileges.categories.get(data.cid, caller.uid),
|
|
categories.getCategoryData(data.cid),
|
|
]);
|
|
if (!category || !userPrivileges.read) {
|
|
return null;
|
|
}
|
|
|
|
return category;
|
|
};
|
|
|
|
categoriesAPI.create = async function (caller, data) {
|
|
await hasAdminPrivilege(caller.uid);
|
|
|
|
const response = await categories.create(data);
|
|
const categoryObjs = await categories.getCategories([response.cid]);
|
|
return categoryObjs[0];
|
|
};
|
|
|
|
categoriesAPI.update = async function (caller, data) {
|
|
await hasAdminPrivilege(caller.uid);
|
|
if (!data) {
|
|
throw new Error('[[error:invalid-data]]');
|
|
}
|
|
const { cid, values } = data;
|
|
|
|
const payload = {};
|
|
payload[cid] = values;
|
|
await categories.update(payload);
|
|
};
|
|
|
|
categoriesAPI.delete = async function (caller, { cid }) {
|
|
await hasAdminPrivilege(caller.uid);
|
|
|
|
const name = await categories.getCategoryField(cid, 'name');
|
|
await categories.purge(cid, caller.uid);
|
|
await events.log({
|
|
type: 'category-purge',
|
|
uid: caller.uid,
|
|
ip: caller.ip,
|
|
cid: cid,
|
|
name: name,
|
|
});
|
|
};
|
|
|
|
categoriesAPI.getTopicCount = async (caller, { cid }) => {
|
|
const allowed = await privileges.categories.can('find', cid, caller.uid);
|
|
if (!allowed) {
|
|
throw new Error('[[error:no-privileges]]');
|
|
}
|
|
const count = await categories.getCategoryField(cid, 'topic_count');
|
|
return { count };
|
|
};
|
|
|
|
categoriesAPI.getPosts = async (caller, { cid }) => await categories.getRecentReplies(cid, caller.uid, 0, 4);
|
|
|
|
categoriesAPI.getChildren = async (caller, { cid, start }) => {
|
|
if (!start || start < 0) {
|
|
start = 0;
|
|
}
|
|
start = parseInt(start, 10);
|
|
|
|
const allowed = await privileges.categories.can('read', cid, caller.uid);
|
|
if (!allowed) {
|
|
throw new Error('[[error:no-privileges]]');
|
|
}
|
|
|
|
const category = await categories.getCategoryData(cid);
|
|
await categories.getChildrenTree(category, caller.uid);
|
|
const allCategories = [];
|
|
categories.flattenCategories(allCategories, category.children);
|
|
await categories.getRecentTopicReplies(allCategories, caller.uid);
|
|
|
|
const payload = category.children.slice(start, start + category.subCategoriesPerPage);
|
|
return { categories: payload };
|
|
};
|
|
|
|
categoriesAPI.getTopics = async (caller, data) => {
|
|
data.query = data.query || {};
|
|
const [userPrivileges, settings, targetUid] = await Promise.all([
|
|
privileges.categories.get(data.cid, caller.uid),
|
|
user.getSettings(caller.uid),
|
|
user.getUidByUserslug(data.query.author),
|
|
]);
|
|
|
|
if (!userPrivileges.read) {
|
|
throw new Error('[[error:no-privileges]]');
|
|
}
|
|
|
|
const infScrollTopicsPerPage = 20;
|
|
const sort = data.sort || data.categoryTopicSort || meta.config.categoryTopicSort || 'newest_to_oldest';
|
|
|
|
let start = Math.max(0, parseInt(data.after || 0, 10));
|
|
|
|
if (parseInt(data.direction, 10) === -1) {
|
|
start -= infScrollTopicsPerPage;
|
|
}
|
|
|
|
let stop = start + infScrollTopicsPerPage - 1;
|
|
|
|
start = Math.max(0, start);
|
|
stop = Math.max(0, stop);
|
|
const result = await categories.getCategoryTopics({
|
|
uid: caller.uid,
|
|
cid: data.cid,
|
|
start,
|
|
stop,
|
|
sort,
|
|
settings,
|
|
query: data.query,
|
|
tag: data.query.tag,
|
|
targetUid,
|
|
});
|
|
categories.modifyTopicsByPrivilege(result.topics, userPrivileges);
|
|
|
|
return { ...result, privileges: userPrivileges };
|
|
};
|
|
|
|
categoriesAPI.setWatchState = async (caller, { cid, state, uid }) => {
|
|
let targetUid = caller.uid;
|
|
const cids = Array.isArray(cid) ? cid.map(cid => parseInt(cid, 10)) : [parseInt(cid, 10)];
|
|
if (uid) {
|
|
targetUid = uid;
|
|
}
|
|
await user.isAdminOrGlobalModOrSelf(caller.uid, targetUid);
|
|
const allCids = await categories.getAllCidsFromSet('categories:cid');
|
|
const categoryData = await categories.getCategoriesFields(allCids, ['cid', 'parentCid']);
|
|
|
|
// filter to subcategories of cid
|
|
let cat;
|
|
do {
|
|
cat = categoryData.find(c => !cids.includes(c.cid) && cids.includes(c.parentCid));
|
|
if (cat) {
|
|
cids.push(cat.cid);
|
|
}
|
|
} while (cat);
|
|
|
|
await user.setCategoryWatchState(targetUid, cids, state);
|
|
await topics.pushUnreadCount(targetUid);
|
|
|
|
return { cids };
|
|
};
|
|
|
|
categoriesAPI.getPrivileges = async (caller, { cid }) => {
|
|
await hasAdminPrivilege(caller.uid, 'privileges');
|
|
|
|
let responsePayload;
|
|
|
|
if (cid === 'admin') {
|
|
responsePayload = await privileges.admin.list(caller.uid);
|
|
} else if (!parseInt(cid, 10)) {
|
|
responsePayload = await privileges.global.list();
|
|
} else {
|
|
responsePayload = await privileges.categories.list(cid);
|
|
}
|
|
|
|
return responsePayload;
|
|
};
|
|
|
|
categoriesAPI.setPrivilege = async (caller, data) => {
|
|
await hasAdminPrivilege(caller.uid, 'privileges');
|
|
|
|
const [userExists, groupExists] = await Promise.all([
|
|
user.exists(data.member),
|
|
groups.exists(data.member),
|
|
]);
|
|
|
|
if (!userExists && !groupExists) {
|
|
throw new Error('[[error:no-user-or-group]]');
|
|
}
|
|
const privs = Array.isArray(data.privilege) ? data.privilege : [data.privilege];
|
|
const type = data.set ? 'give' : 'rescind';
|
|
if (!privs.length) {
|
|
throw new Error('[[error:invalid-data]]');
|
|
}
|
|
if (parseInt(data.cid, 10) === 0) {
|
|
const adminPrivList = await privileges.admin.getPrivilegeList();
|
|
const adminPrivs = privs.filter(priv => adminPrivList.includes(priv));
|
|
if (adminPrivs.length) {
|
|
await privileges.admin[type](adminPrivs, data.member);
|
|
}
|
|
const globalPrivList = await privileges.global.getPrivilegeList();
|
|
const globalPrivs = privs.filter(priv => globalPrivList.includes(priv));
|
|
if (globalPrivs.length) {
|
|
await privileges.global[type](globalPrivs, data.member);
|
|
}
|
|
} else {
|
|
const categoryPrivList = await privileges.categories.getPrivilegeList();
|
|
const categoryPrivs = privs.filter(priv => categoryPrivList.includes(priv));
|
|
await privileges.categories[type](categoryPrivs, data.cid, data.member);
|
|
}
|
|
|
|
await events.log({
|
|
uid: caller.uid,
|
|
type: 'privilege-change',
|
|
ip: caller.ip,
|
|
privilege: data.privilege.toString(),
|
|
cid: data.cid,
|
|
action: data.set ? 'grant' : 'rescind',
|
|
target: data.member,
|
|
});
|
|
};
|
|
|
|
categoriesAPI.setModerator = async (caller, { cid, member, set }) => {
|
|
await hasAdminPrivilege(caller.uid, 'admins-mods');
|
|
|
|
const privilegeList = await privileges.categories.getUserPrivilegeList();
|
|
await categoriesAPI.setPrivilege(caller, { cid, privilege: privilegeList, member, set });
|
|
};
|