diff --git a/src/api/index.js b/src/api/index.js index 9e5446c325..c454de93a5 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -9,6 +9,7 @@ module.exports = { posts: require('./posts'), chats: require('./chats'), categories: require('./categories'), + search: require('./search'), flags: require('./flags'), files: require('./files'), utils: require('./utils'), diff --git a/src/api/search.js b/src/api/search.js new file mode 100644 index 0000000000..8dad8eb44e --- /dev/null +++ b/src/api/search.js @@ -0,0 +1,104 @@ +'use strict'; + +const _ = require('lodash'); + +const categories = require('../categories'); +const privileges = require('../privileges'); +const meta = require('../meta'); +const plugins = require('../plugins'); + +const controllersHelpers = require('../controllers/helpers'); + +const searchApi = module.exports; + +searchApi.categories = async (caller, data) => { + // used by categorySearch module + + let cids = []; + let matchedCids = []; + const privilege = data.privilege || 'topics:read'; + data.states = (data.states || ['watching', 'notwatching', 'ignoring']).map( + state => categories.watchStates[state] + ); + + if (data.search) { + ({ cids, matchedCids } = await findMatchedCids(caller.uid, data)); + } else { + cids = await loadCids(caller.uid, data.parentCid); + } + + const visibleCategories = await controllersHelpers.getVisibleCategories({ + cids, uid: caller.uid, states: data.states, privilege, showLinks: data.showLinks, parentCid: data.parentCid, + }); + + if (Array.isArray(data.selectedCids)) { + data.selectedCids = data.selectedCids.map(cid => parseInt(cid, 10)); + } + + let categoriesData = categories.buildForSelectCategories(visibleCategories, ['disabledClass'], data.parentCid); + categoriesData = categoriesData.slice(0, 200); + + categoriesData.forEach((category) => { + category.selected = data.selectedCids ? data.selectedCids.includes(category.cid) : false; + if (matchedCids.includes(category.cid)) { + category.match = true; + } + }); + const result = await plugins.hooks.fire('filter:categories.categorySearch', { + categories: categoriesData, + ...data, + uid: caller.uid, + }); + + return { categories: result.categories }; +}; + +async function findMatchedCids(uid, data) { + const result = await categories.search({ + uid: uid, + query: data.search, + qs: data.query, + paginate: false, + }); + + let matchedCids = result.categories.map(c => c.cid); + // no need to filter if all 3 states are used + const filterByWatchState = !Object.values(categories.watchStates) + .every(state => data.states.includes(state)); + + if (filterByWatchState) { + const states = await categories.getWatchState(matchedCids, uid); + matchedCids = matchedCids.filter((cid, index) => data.states.includes(states[index])); + } + + const rootCids = _.uniq(_.flatten(await Promise.all(matchedCids.map(categories.getParentCids)))); + const allChildCids = _.uniq(_.flatten(await Promise.all(matchedCids.map(categories.getChildrenCids)))); + + return { + cids: _.uniq(rootCids.concat(allChildCids).concat(matchedCids)), + matchedCids: matchedCids, + }; +} + +async function loadCids(uid, parentCid) { + let resultCids = []; + async function getCidsRecursive(cids) { + const categoryData = await categories.getCategoriesFields(cids, ['subCategoriesPerPage']); + const cidToData = _.zipObject(cids, categoryData); + await Promise.all(cids.map(async (cid) => { + const allChildCids = await categories.getAllCidsFromSet(`cid:${cid}:children`); + if (allChildCids.length) { + const childCids = await privileges.categories.filterCids('find', allChildCids, uid); + resultCids.push(...childCids.slice(0, cidToData[cid].subCategoriesPerPage)); + await getCidsRecursive(childCids); + } + })); + } + + const allRootCids = await categories.getAllCidsFromSet(`cid:${parentCid}:children`); + const rootCids = await privileges.categories.filterCids('find', allRootCids, uid); + const pageCids = rootCids.slice(0, meta.config.categoriesPerPage); + resultCids = pageCids; + await getCidsRecursive(pageCids); + return resultCids; +} diff --git a/src/controllers/write/index.js b/src/controllers/write/index.js index 46a8dd8110..26c74128d8 100644 --- a/src/controllers/write/index.js +++ b/src/controllers/write/index.js @@ -10,6 +10,7 @@ Write.tags = require('./tags'); Write.posts = require('./posts'); Write.chats = require('./chats'); Write.flags = require('./flags'); +Write.search = require('./search'); Write.admin = require('./admin'); Write.files = require('./files'); Write.utilities = require('./utilities'); diff --git a/src/controllers/write/search.js b/src/controllers/write/search.js new file mode 100644 index 0000000000..a6acd0a59a --- /dev/null +++ b/src/controllers/write/search.js @@ -0,0 +1,10 @@ +'use strict'; + +const api = require('../../api'); +const helpers = require('../helpers'); + +const Search = module.exports; + +Search.categories = async (req, res) => { + helpers.formatApiResponse(200, res, await api.search.categories(req, req.query)); +}; diff --git a/src/routes/write/index.js b/src/routes/write/index.js index 8e29c3ddd1..2ebec74ce1 100644 --- a/src/routes/write/index.js +++ b/src/routes/write/index.js @@ -41,6 +41,7 @@ Write.reload = async (params) => { router.use('/api/v3/posts', require('./posts')()); router.use('/api/v3/chats', require('./chats')()); router.use('/api/v3/flags', require('./flags')()); + router.use('/api/v3/search', require('./search')()); router.use('/api/v3/admin', require('./admin')()); router.use('/api/v3/files', require('./files')()); router.use('/api/v3/utilities', require('./utilities')()); diff --git a/src/routes/write/search.js b/src/routes/write/search.js new file mode 100644 index 0000000000..01b98cdeed --- /dev/null +++ b/src/routes/write/search.js @@ -0,0 +1,19 @@ +'use strict'; + +const router = require('express').Router(); +// const middleware = require('../../middleware'); +const controllers = require('../../controllers'); +const routeHelpers = require('../helpers'); + +const { setupApiRoute } = routeHelpers; + +module.exports = function () { + // const middlewares = []; + + // maybe redirect to /search/posts? + // setupApiRoute(router, 'post', '/', [...middlewares], controllers.write.search.TBD); + + setupApiRoute(router, 'get', '/categories', [], controllers.write.search.categories); + + return router; +}; diff --git a/src/socket.io/categories/search.js b/src/socket.io/categories/search.js index ad04c20edf..dbb355ce89 100644 --- a/src/socket.io/categories/search.js +++ b/src/socket.io/categories/search.js @@ -1,101 +1,13 @@ 'use strict'; -const _ = require('lodash'); - -const meta = require('../../meta'); -const categories = require('../../categories'); -const privileges = require('../../privileges'); -const controllersHelpers = require('../../controllers/helpers'); -const plugins = require('../../plugins'); +const sockets = require('..'); +const api = require('../../api'); module.exports = function (SocketCategories) { - // used by categorySearch module SocketCategories.categorySearch = async function (socket, data) { - let cids = []; - let matchedCids = []; - const privilege = data.privilege || 'topics:read'; - data.states = (data.states || ['watching', 'notwatching', 'ignoring']).map( - state => categories.watchStates[state] - ); + sockets.warnDeprecated(socket, 'GET /api/v3/search/categories'); - if (data.search) { - ({ cids, matchedCids } = await findMatchedCids(socket.uid, data)); - } else { - cids = await loadCids(socket.uid, data.parentCid); - } - - const visibleCategories = await controllersHelpers.getVisibleCategories({ - cids, uid: socket.uid, states: data.states, privilege, showLinks: data.showLinks, parentCid: data.parentCid, - }); - - if (Array.isArray(data.selectedCids)) { - data.selectedCids = data.selectedCids.map(cid => parseInt(cid, 10)); - } - - let categoriesData = categories.buildForSelectCategories(visibleCategories, ['disabledClass'], data.parentCid); - categoriesData = categoriesData.slice(0, 200); - - categoriesData.forEach((category) => { - category.selected = data.selectedCids ? data.selectedCids.includes(category.cid) : false; - if (matchedCids.includes(category.cid)) { - category.match = true; - } - }); - const result = await plugins.hooks.fire('filter:categories.categorySearch', { - categories: categoriesData, - ...data, - uid: socket.uid, - }); - return result.categories; + const { categories } = await api.search.categories(socket, data); + return categories; }; - - async function findMatchedCids(uid, data) { - const result = await categories.search({ - uid: uid, - query: data.search, - qs: data.query, - paginate: false, - }); - - let matchedCids = result.categories.map(c => c.cid); - // no need to filter if all 3 states are used - const filterByWatchState = !Object.values(categories.watchStates) - .every(state => data.states.includes(state)); - - if (filterByWatchState) { - const states = await categories.getWatchState(matchedCids, uid); - matchedCids = matchedCids.filter((cid, index) => data.states.includes(states[index])); - } - - const rootCids = _.uniq(_.flatten(await Promise.all(matchedCids.map(categories.getParentCids)))); - const allChildCids = _.uniq(_.flatten(await Promise.all(matchedCids.map(categories.getChildrenCids)))); - - return { - cids: _.uniq(rootCids.concat(allChildCids).concat(matchedCids)), - matchedCids: matchedCids, - }; - } - - async function loadCids(uid, parentCid) { - let resultCids = []; - async function getCidsRecursive(cids) { - const categoryData = await categories.getCategoriesFields(cids, ['subCategoriesPerPage']); - const cidToData = _.zipObject(cids, categoryData); - await Promise.all(cids.map(async (cid) => { - const allChildCids = await categories.getAllCidsFromSet(`cid:${cid}:children`); - if (allChildCids.length) { - const childCids = await privileges.categories.filterCids('find', allChildCids, uid); - resultCids.push(...childCids.slice(0, cidToData[cid].subCategoriesPerPage)); - await getCidsRecursive(childCids); - } - })); - } - - const allRootCids = await categories.getAllCidsFromSet(`cid:${parentCid}:children`); - const rootCids = await privileges.categories.filterCids('find', allRootCids, uid); - const pageCids = rootCids.slice(0, meta.config.categoriesPerPage); - resultCids = pageCids; - await getCidsRecursive(pageCids); - return resultCids; - } };