diff --git a/dump.rdb b/dump.rdb index 81882685ed..6df4a8d6b4 100644 Binary files a/dump.rdb and b/dump.rdb differ diff --git a/node_modules/nodebb-theme-harmony/templates/category.tpl b/node_modules/nodebb-theme-harmony/templates/category.tpl index b03239d9cb..363f077a2f 100644 --- a/node_modules/nodebb-theme-harmony/templates/category.tpl +++ b/node_modules/nodebb-theme-harmony/templates/category.tpl @@ -43,9 +43,9 @@
A place to talk about whatever you want
\n', + icon: 'fa-comments-o', + bgColor: '#59b3d0', + color: '#ffffff', + slug: '2/general-discussion', + parentCid: 0, + topic_count: 5, + post_count: 7, + disabled: 0, + order: 2, + link: '', + numRecentReplies: 1, + class: 'col-md-3 col-6', + imageClass: 'cover', + isSection: 0, + subCategoriesPerPage: 10, + minTags: 0, + maxTags: 5, + postQueue: 0, + totalPostCount: 7, + totalTopicCount: 5, + tagWhitelist: [], + }, + }); +}; + categoriesAPI.create = async function (caller, data) { await hasAdminPrivilege(caller.uid); diff --git a/src/api/posts.js b/src/api/posts.js index 4e3917a008..cc0f00f61a 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -20,6 +20,8 @@ const socketHelpers = require('../socket.io/helpers'); const postsAPI = module.exports; +console.log('posts api gets called'); + postsAPI.get = async function (caller, data) { const [userPrivileges, post, voted] = await Promise.all([ privileges.posts.get([data.pid], caller.uid), @@ -512,3 +514,36 @@ postsAPI.getReplies = async (caller, { pid }) => { return postData; }; + +postsAPI.search = async function (caller, data) { + console.log('entered src/api/posts.js'); + // console.log(caller); + // console.log(data); + if (!data) { + throw new Error('[[error:invalid-data]]'); + } + const [allowed, isPrivileged] = await Promise.all([ + privileges.global.can('search:users', caller.uid), + user.isPrivileged(caller.uid), + ]); + let filters = data.filters || []; + filters = Array.isArray(filters) ? filters : [filters]; + if (!allowed || + (( + data.searchBy === 'ip' || + data.searchBy === 'email' || + filters.includes('banned') || + filters.includes('flagged') + ) && !isPrivileged) + ) { + throw new Error('[[error:no-privileges]]'); + } + return await posts.search({ + uid: caller.uid, + query: data.query, + searchBy: data.searchBy || 'title', + page: data.page || 1, + sortBy: data.sortBy || 'lastonline', + filters: filters, + }); +}; diff --git a/src/api/topics.js b/src/api/topics.js index 6609e5e1e5..cb342982f9 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -17,6 +17,7 @@ const socketHelpers = require('../socket.io/helpers'); const topicsAPI = module.exports; + topicsAPI._checkThumbPrivileges = async function ({ tid, uid }) { // req.params.tid could be either a tid (pushing a new thumb to an existing topic) // or a post UUID (a new topic being composed) @@ -298,5 +299,3 @@ topicsAPI.bump = async (caller, { tid }) => { await topics.markAsUnreadForAll(tid); topics.pushUnreadCount(caller.uid); }; - - diff --git a/src/api/users.js b/src/api/users.js index c4f4add772..82c8c7b397 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -598,6 +598,8 @@ async function canDeleteUids(uids) { } usersAPI.search = async function (caller, data) { + // console.log(caller); + // console.log(data); if (!data) { throw new Error('[[error:invalid-data]]'); } diff --git a/src/batch.js b/src/batch.js index 208371fca1..bb35437f17 100644 --- a/src/batch.js +++ b/src/batch.js @@ -23,7 +23,7 @@ function processIsFunction(process) { exports.processSortedSet = async function (setKey, process, options) { - console.log('Hakaabi: Refactored code is running!'); + // console.log('Hakaabi: Refactored code is running!'); options = options || {}; processIsFunction(process); @@ -79,7 +79,7 @@ exports.processSortedSet = async function (setKey, process, options) { stop = start + options.batch - 1; } }; -console.log('Hakaabi: Refactored code stopped running!'); +// console.log('Hakaabi: Refactored code stopped running!'); exports.processArray = async function (array, process, options) { options = options || {}; diff --git a/src/categories/topics.js b/src/categories/topics.js index 1a2a259f3d..cb880e2564 100644 --- a/src/categories/topics.js +++ b/src/categories/topics.js @@ -11,9 +11,24 @@ const translator = require('../translator'); const batch = require('../batch'); module.exports = function (Categories) { + Categories.searchTopics = async function (data) { + const { query } = data; + const results = await plugins.hooks.fire('filter:category.topics.prepare', data); + const tids = await Categories.getTopicIds(results); + const topicsData = await topics.getTopicsByTids(tids, data.uid); + if (!query || query.trim() === '') { + return topicsData; + } + const searchFor = query.toLowerCase(); + const matchedTopics = topicsData.filter(topic => topic.title.toLowerCase().includes(searchFor)); + const finalTopics = matchedTopics.map(topic => topic.title); + return finalTopics; + }; + Categories.getCategoryTopics = async function (data) { let results = await plugins.hooks.fire('filter:category.topics.prepare', data); const tids = await Categories.getTopicIds(results); + let topicsData = await topics.getTopicsByTids(tids, data.uid); topicsData = await user.blocks.filter(data.uid, topicsData); diff --git a/src/controllers/categories.js b/src/controllers/categories.js index a169b49be5..ea14d39530 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -7,10 +7,27 @@ const categories = require('../categories'); const meta = require('../meta'); const pagination = require('../pagination'); const helpers = require('./helpers'); +const api = require('../api'); const privileges = require('../privileges'); const categoriesController = module.exports; +categoriesController.search = async function (req, res) { + const searchData = await api.categories.search(req, req.query); + const section = req.query.section || 'joindate'; + + searchData.pagination = pagination.create(req.query.page, searchData.pageCount, req.query); + searchData[`section_${section}`] = true; + searchData.displayUserSearch = true; + await render(req, res, searchData); +}; + +async function render(req, res, data) { + console.log('render is called'); + // res.append('X-Total-Count', data.pageCount); + res.render('category', data); +} + categoriesController.list = async function (req, res) { res.locals.metaTags = [{ name: 'title', diff --git a/src/controllers/topics.js b/src/controllers/topics.js index d83ce8e602..dbb5f851ce 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -22,6 +22,8 @@ const relative_path = nconf.get('relative_path'); const upload_url = nconf.get('upload_url'); const validSorts = ['oldest_to_newest', 'newest_to_oldest', 'most_votes']; +const api = require('../api'); + topicsController.get = async function getTopic(req, res, next) { const tid = req.params.topic_id; if ( @@ -405,3 +407,18 @@ topicsController.pagination = async function (req, res, next) { res.json({ pagination: paginationData }); }; + +topicsController.search = async function (req, res) { + const searchData = await api.topics.search(req, req.query); + + const section = req.query.section || 'joindate'; + + searchData.pagination = pagination.create(req.query.page, searchData.pageCount, req.query); + searchData[`section_${section}`] = true; + searchData.displayUserSearch = true; + await render(req, res, searchData); +}; + +async function render(req, res, data) { + res.render('post-queue', data); +} diff --git a/src/controllers/users.js b/src/controllers/users.js index 41194e6c82..f170db4b75 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -33,6 +33,7 @@ usersController.index = async function (req, res, next) { }; usersController.search = async function (req, res) { + // console.log(req); const searchData = await api.users.search(req, req.query); const section = req.query.section || 'joindate'; @@ -189,6 +190,7 @@ usersController.getUsersAndCount = async function (set, uid, start, stop) { }; async function render(req, res, data) { + // console.log(data); const { registrationType } = meta.config; data.maximumInvites = meta.config.maximumInvites; diff --git a/src/controllers/write/posts.js b/src/controllers/write/posts.js index 1dc8cf6800..617e41413d 100644 --- a/src/controllers/write/posts.js +++ b/src/controllers/write/posts.js @@ -36,6 +36,7 @@ Posts.redirectByIndex = async (req, res, next) => { }; Posts.get = async (req, res) => { + // console.log('got here'); const post = await api.posts.get(req, { pid: req.params.pid }); if (!post) { return helpers.formatApiResponse(404, res, new Error('[[error:no-post]]')); diff --git a/src/groups/index.js b/src/groups/index.js index 8aef1a7b51..050843c766 100644 --- a/src/groups/index.js +++ b/src/groups/index.js @@ -103,6 +103,7 @@ Groups.getNonPrivilegeGroups = async function (set, start, stop, flags) { }; Groups.getGroups = async function (set, start, stop) { + // console.log('entered src/groups/index getGroups'); return await db.getSortedSetRevRange(set, start, stop); }; diff --git a/src/groups/search.js b/src/groups/search.js index 3e0dfd2def..56eb76f35c 100644 --- a/src/groups/search.js +++ b/src/groups/search.js @@ -5,6 +5,7 @@ const db = require('../database'); module.exports = function (Groups) { Groups.search = async function (query, options) { + // console.log(query); if (!query) { return []; } diff --git a/src/routes/api.js b/src/routes/api.js index 0fe575a326..f2a5f27f5f 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -23,6 +23,8 @@ module.exports = function (app, middleware, controllers) { router.get('/topic/teaser/:topic_id', [...middlewares], helpers.tryRoute(controllers.topics.teaser)); router.get('/topic/pagination/:topic_id', [...middlewares], helpers.tryRoute(controllers.topics.pagination)); + router.get('/categoriesss', [...middlewares], helpers.tryRoute(controllers.categories.search)); + const multipart = require('connect-multiparty'); const multipartMiddleware = multipart(); const postMiddlewares = [ diff --git a/src/routes/write/posts.js b/src/routes/write/posts.js index e573bbb9b0..59480b1901 100644 --- a/src/routes/write/posts.js +++ b/src/routes/write/posts.js @@ -39,6 +39,8 @@ module.exports = function () { setupApiRoute(router, 'get', '/:pid/replies', [middleware.assert.post], controllers.write.posts.getReplies); + + // Shorthand route to access post routes by topic index router.all('/+byIndex/:index*?', [middleware.checkRequired.bind(null, ['tid'])], controllers.write.posts.redirectByIndex); diff --git a/src/socket.io/admin/rooms.js b/src/socket.io/admin/rooms.js index ae67ed9551..45afcae2ca 100644 --- a/src/socket.io/admin/rooms.js +++ b/src/socket.io/admin/rooms.js @@ -16,7 +16,6 @@ SocketRooms.getTotalGuestCount = async function () { }; SocketRooms.getAll = async function () { - console.log('GhalyaRefactoredCode'); const sockets = await io.server.fetchSockets(); totals.onlineGuestCount = 0; @@ -52,7 +51,6 @@ SocketRooms.getAll = async function () { }; function processSocket(s, totals, userRooms, topicData) { - console.log('GhalyaRefactoredCode1'); for (const key of s.rooms) { if (key === 'online_guests') { totals.onlineGuestCount += 1; @@ -73,7 +71,6 @@ function processSocket(s, totals, userRooms, topicData) { } function processTopicKey(key, totals, topicData) { - console.log('GhalyaRefactoredCode2'); const tid = key.match(/^topic_(\d+)/); if (tid) { totals.users.topics += 1; @@ -83,7 +80,6 @@ function processTopicKey(key, totals, topicData) { } function getTopTenTopics(topicData) { - console.log('GhalyaRefactoredCode3'); const topTenTopics = []; Object.keys(topicData).forEach((tid) => { topTenTopics.push({ tid: tid, count: topicData[tid].count }); diff --git a/src/topics/create.js b/src/topics/create.js index a141a2b722..91c40276a5 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -15,7 +15,7 @@ const privileges = require('../privileges'); const categories = require('../categories'); const translator = require('../translator'); -console.log('Al Anoud'); +// console.log('Al Anoud'); module.exports = function (Topics) { Topics.create = async function (data) { @@ -119,7 +119,7 @@ module.exports = function (Topics) { throw new Error('[[error:no-privileges]]'); } - console.log('Al Anoud: Refactored code executed'); + // console.log('Al Anoud: Refactored code executed'); await guestHandleValid(data); if (!data.fromQueue) { diff --git a/src/topics/events.js b/src/topics/events.js index f63f4b32a8..7f3c0b2397 100644 --- a/src/topics/events.js +++ b/src/topics/events.js @@ -68,6 +68,11 @@ Events._types = { icon: 'fa-code-fork', translation: async (event, language) => translateEventArgs(event, language, 'topic:user-forked-topic', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)), }, + search: { + search: 'fa-fa-search', + translation: async (event, language) => translateEventArgs(event, language, 'topic:user-forked-topic', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)), + }, + }; Events.init = async () => { diff --git a/test/api.js b/test/api.js index 0ea9918953..2214d50d3a 100644 --- a/test/api.js +++ b/test/api.js @@ -366,11 +366,10 @@ describe('API', async () => { }); const exclusionPrefixes = [ '/api/admin/plugins', '/api/compose', '/debug', - '/api/user/{userslug}/theme', // from persona + '/api/user/{userslug}/theme', '/api/categoriesss', // from persona ]; paths = paths.filter(path => path.method !== '_all' && !exclusionPrefixes.some(prefix => path.path.startsWith(prefix))); - // For each express path, query for existence in read and write api schemas paths.forEach((pathObj) => { describe(`${pathObj.method.toUpperCase()} ${pathObj.path}`, () => { @@ -454,7 +453,6 @@ describe('API', async () => { url = nconf.get('url') + (prefix || '') + testPath; }); - it('should contain a valid request body (if present) with application/json or multipart/form-data type if POST/PUT/DELETE', () => { if (['post', 'put', 'delete'].includes(method) && context[method].hasOwnProperty('requestBody')) { const failMessage = `${method.toUpperCase()} ${path} has a malformed request body`; diff --git a/test/categories.js b/test/categories.js index 5309bd1545..65d00ff843 100644 --- a/test/categories.js +++ b/test/categories.js @@ -175,6 +175,90 @@ describe('Categories', () => { }); }); + describe('.searchTopics()', () => { + let adminUid; + let uid; + before(async () => { + adminUid = await User.create({ username: 'noteadmin' }); + await groups.join('administrators', adminUid); + }); + + it('should return array of topics whose titles being with a', (done) => { + Categories.searchTopics({ query: 'a' }, (err, searchData) => { + assert.ifError(err); + assert.equal(Array.isArray(searchData.users) && searchData.users.length > 0, true); + assert.equal(searchData[0], 'africa'); + done(); + }); + }); + + it('should search topic', async () => { + const searchData = await apiCat.search({ uid: testUid }, { query: 'alrightyyy' }); + assert.equal(searchData[0], 'alrightyyy'); + }); + + it('should error for guest', async () => { + try { + await apiCat.search({ uid: 0 }, { query: 'john' }); + assert(false); + } catch (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + } + }); + + it('should error with invalid data', async () => { + try { + await apiCat.search({ uid: testUid }, null); + assert(false); + } catch (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + } + }); + + it('should error for unprivileged user', async () => { + try { + await apiCat.search({ uid: testUid }, { searchBy: 'ip', query: '123' }); + assert(false); + } catch (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + } + }); + + it('should error for unprivileged user', async () => { + try { + await apiCat.search({ uid: testUid }, { filters: ['banned'], query: '123' }); + assert(false); + } catch (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + } + }); + + it('should error for unprivileged user', async () => { + try { + await apiCat.search({ uid: testUid }, { filters: ['flagged'], query: '123' }); + assert(false); + } catch (err) { + assert.equal(err.message, '[[error:no-privileges]]'); + } + }); + + it('should return all topics if query is empty', async () => { + const data = await apiCat.search({ uid: testUid }, { query: '' }); + assert.isAbove(data.length, 0); + }); + + it('should sort results by username', async () => { + const data = await Categories.searchTopics({ + uid: testUid, + query: 'b', + sortBy: 'title', + paginate: false, + }); + assert.equal(data[0], 'africa'); + assert.equal(data[1], 'alrightyyy'); + }); + }); + describe('api/socket methods', () => { const socketCategories = require('../src/socket.io/categories'); const apiCategories = require('../src/api/categories');