|
2 | 2 |
|
3 | 3 | const nconf = require('nconf'); |
4 | 4 |
|
| 5 | +// ⬇️ These paths assume this file is at src/controllers/recent.js |
5 | 6 | const user = require('../user'); |
6 | 7 | const topics = require('../topics'); |
7 | 8 | const meta = require('../meta'); |
8 | | -const helpers = require('./helpers'); |
9 | | -const pagination = require('../pagination'); |
10 | 9 | const privileges = require('../privileges'); |
| 10 | +// If helpers.js is NOT in the same folder as this file, change to: ../helpers |
| 11 | +const helpers = require('./helpers'); |
11 | 12 |
|
12 | 13 | const recentController = module.exports; |
13 | 14 | const relative_path = nconf.get('relative_path'); |
14 | 15 |
|
15 | | -function setTitleAndBreadcrumbs(data, url, asHome) { |
16 | | - if (asHome) { |
17 | | - data.title = meta.config.homePageTitle || '[[pages:home]]'; |
18 | | - return; |
19 | | - } |
20 | | - data.title = `[[pages:${url}]]`; |
21 | | - data.breadcrumbs = helpers.buildBreadcrumbs([{ text: `[[${url}:title]]` }]); |
22 | | -} |
| 16 | +// --------------------------------------------------------------------------- |
| 17 | +// Small guards/utilities |
| 18 | +// --------------------------------------------------------------------------- |
| 19 | + |
| 20 | +function resolveTerm(req) { |
| 21 | + // Some NodeBB trees expose helpers.terms; some don’t. |
| 22 | + // We gracefully fall back to 'alltime' and reject unknown terms when a key is supplied. |
| 23 | + const termKey = req.query.term; |
| 24 | + if (!termKey) return 'alltime'; |
| 25 | + |
| 26 | + if (helpers && helpers.terms && Object.prototype.hasOwnProperty.call(helpers.terms, termKey)) { |
| 27 | + return helpers.terms[termKey] || 'alltime'; |
| 28 | + } |
23 | 29 |
|
| 30 | + // If a term was provided but we can't validate it, mimic original behavior: reject. |
| 31 | + return null; |
| 32 | +} |
24 | 33 |
|
25 | | -function setRssFields(ctx) { |
26 | | - const { data, url, req, rssToken } = ctx; |
27 | | - const disabled = meta.config['feeds:disableRSS'] || 0; |
| 34 | +function setTitleAndBreadcrumbs(data, url, asHome) { |
| 35 | + if (asHome) { |
| 36 | + data.title = meta.config.homePageTitle || '[[pages:home]]'; |
| 37 | + return; |
| 38 | + } |
| 39 | + data.title = `[[pages:${url}]]`; |
| 40 | + if (helpers && typeof helpers.buildBreadcrumbs === 'function') { |
| 41 | + data.breadcrumbs = helpers.buildBreadcrumbs([{ text: `[[${url}:title]]` }]); |
| 42 | + } |
| 43 | +} |
28 | 44 |
|
29 | | - data['feeds:disableRSS'] = disabled; |
30 | | - if (disabled) return; |
| 45 | +function setRssFields({ data, url, req, rssToken }) { |
| 46 | + const disabled = meta.config['feeds:disableRSS'] || 0; |
| 47 | + data['feeds:disableRSS'] = disabled; |
| 48 | + if (disabled) return; |
31 | 49 |
|
32 | | - let rss = `${relative_path}/${url}.rss`; |
33 | | - if (req.loggedIn) { |
34 | | - rss += `?uid=${req.uid}&token=${rssToken}`; |
35 | | - } |
36 | | - data.rssFeedUrl = rss; |
| 50 | + let rss = `${relative_path}/${url}.rss`; |
| 51 | + if (req.loggedIn) { |
| 52 | + rss += `?uid=${req.uid}&token=${rssToken}`; |
| 53 | + } |
| 54 | + data.rssFeedUrl = rss; |
37 | 55 | } |
38 | 56 |
|
39 | | -recentController.get = async function (req, res, next) { |
40 | | - const data = await recentController.getData(req, 'recent', 'recent'); |
41 | | - if (!data) { |
42 | | - return next(); |
43 | | - } |
| 57 | +// --------------------------------------------------------------------------- |
| 58 | +// Routes |
| 59 | +// --------------------------------------------------------------------------- |
44 | 60 |
|
45 | | - res.render('recent', data); |
| 61 | +recentController.get = async function (req, res, next) { |
| 62 | + const data = await recentController.getData(req, 'recent', 'recent'); |
| 63 | + if (!data) return next(); |
| 64 | + res.render('recent', data); |
46 | 65 | }; |
47 | 66 |
|
48 | 67 | recentController.getData = async function (req, url, sort) { |
49 | | - const page = parseInt(req.query.page, 10) || 1; |
50 | | - |
51 | | - |
52 | | - const termKey = req.query.term; |
53 | | - let term = termKey ? helpers.terms[termKey] : 'alltime'; |
54 | | - if (termKey && !term) return null; |
55 | | - |
56 | | - const { cid, tag } = req.query; |
57 | | - const filter = req.query.filter || ''; |
58 | | - |
59 | | - const [settings, categoryData, tagData, rssToken, canPost, isPrivileged] = await Promise.all([ |
60 | | - user.getSettings(req.uid), |
61 | | - helpers.getSelectedCategory(cid), |
62 | | - helpers.getSelectedTag(tag), |
63 | | - user.auth.getFeedToken(req.uid), |
64 | | - privileges.categories.canPostTopic(req.uid), |
65 | | - user.isPrivileged(req.uid), |
66 | | - ]); |
67 | | - |
68 | | - const start = Math.max(0, (page - 1) * settings.topicsPerPage); |
69 | | - const stop = start + settings.topicsPerPage - 1; |
70 | | - |
71 | | - const data = await topics.getSortedTopics({ |
72 | | - cids: cid, |
73 | | - tags: tag, |
74 | | - uid: req.uid, |
75 | | - start: start, |
76 | | - stop: stop, |
77 | | - filter: filter, |
78 | | - term: term, |
79 | | - sort: sort, |
80 | | - floatPinned: req.query.pinned, |
81 | | - query: req.query, |
82 | | - }); |
83 | | - |
84 | | - const asHome = !(req.originalUrl.startsWith(`${relative_path}/api/${url}`) || req.originalUrl.startsWith(`${relative_path}/${url}`)); |
85 | | - const baseUrl = asHome ? '' : url; |
86 | | - setTitleAndBreadcrumbs(data, url, asHome); |
87 | | - |
88 | | - const query = { ...req.query }; |
89 | | - delete query.page; |
90 | | - data.canPost = canPost; |
91 | | - data.showSelect = isPrivileged; |
92 | | - data.showTopicTools = isPrivileged; |
93 | | - data.allCategoriesUrl = baseUrl + helpers.buildQueryString(query, 'cid', ''); |
94 | | - data.selectedCategory = categoryData.selectedCategory; |
95 | | - data.selectedCids = categoryData.selectedCids; |
96 | | - data.selectedTag = tagData.selectedTag; |
97 | | - data.selectedTags = tagData.selectedTags; |
98 | | - |
99 | | - |
100 | | - setRssFields({ data, url, req, rssToken }); |
101 | | - |
102 | | - data.filters = helpers.buildFilters(baseUrl, filter, query); |
103 | | - data.selectedFilter = data.filters.find(filter => filter && filter.selected); |
104 | | - data.terms = helpers.buildTerms(baseUrl, term, query); |
105 | | - data.selectedTerm = data.terms.find(term => term && term.selected); |
106 | | - |
107 | | - const pageCount = Math.max(1, Math.ceil(data.topicCount / settings.topicsPerPage)); |
108 | | - data.pagination = pagination.create(page, pageCount, req.query); |
109 | | - helpers.addLinkTags({ |
110 | | - url: url, |
111 | | - res: req.res, |
112 | | - tags: data.pagination.rel, |
113 | | - page: page, |
114 | | - }); |
115 | | - return data; |
| 68 | + const page = parseInt(req.query.page, 10) || 1; |
| 69 | + |
| 70 | + // Term handling with guards (no duplicate lets) |
| 71 | + const term = resolveTerm(req); |
| 72 | + if (req.query.term && !term) { |
| 73 | + // termKey provided but invalid |
| 74 | + return null; |
| 75 | + } |
| 76 | + |
| 77 | + const { cid, tag } = req.query; |
| 78 | + const activeFilter = req.query.filter || ''; |
| 79 | + |
| 80 | + const [ |
| 81 | + settings, |
| 82 | + categoryData, |
| 83 | + tagData, |
| 84 | + rssToken, |
| 85 | + canPost, |
| 86 | + isPrivileged, |
| 87 | + ] = await Promise.all([ |
| 88 | + user.getSettings(req.uid), |
| 89 | + helpers.getSelectedCategory ? helpers.getSelectedCategory(cid) : { selectedCategory: null, selectedCids: [] }, |
| 90 | + helpers.getSelectedTag ? helpers.getSelectedTag(tag) : { selectedTag: null, selectedTags: [] }, |
| 91 | + user.auth.getFeedToken(req.uid), |
| 92 | + privileges.categories.canPostTopic(req.uid), |
| 93 | + user.isPrivileged(req.uid), |
| 94 | + ]); |
| 95 | + |
| 96 | + const start = Math.max(0, (page - 1) * settings.topicsPerPage); |
| 97 | + const stop = start + settings.topicsPerPage - 1; |
| 98 | + |
| 99 | + const data = await topics.getSortedTopics({ |
| 100 | + cids: cid, |
| 101 | + tags: tag, |
| 102 | + uid: req.uid, |
| 103 | + start, |
| 104 | + stop, |
| 105 | + filter: activeFilter, |
| 106 | + term, |
| 107 | + sort, |
| 108 | + floatPinned: req.query.pinned, |
| 109 | + query: req.query, |
| 110 | + }); |
| 111 | + |
| 112 | + // Compute "as home" only once; use helper for title & breadcrumbs |
| 113 | + const asHome = !(req.originalUrl.startsWith(`${relative_path}/api/${url}`) || |
| 114 | + req.originalUrl.startsWith(`${relative_path}/${url}`)); |
| 115 | + const baseUrl = asHome ? '' : url; |
| 116 | + setTitleAndBreadcrumbs(data, url, asHome); |
| 117 | + |
| 118 | + // Build query for links |
| 119 | + const query = { ...req.query }; |
| 120 | + delete query.page; |
| 121 | + |
| 122 | + // Permissions / selections |
| 123 | + data.canPost = canPost; |
| 124 | + data.showSelect = isPrivileged; |
| 125 | + data.showTopicTools = isPrivileged; |
| 126 | + |
| 127 | + data.allCategoriesUrl = baseUrl + (helpers.buildQueryString ? helpers.buildQueryString(query, 'cid', '') : ''); |
| 128 | + data.selectedCategory = categoryData.selectedCategory; |
| 129 | + data.selectedCids = categoryData.selectedCids; |
| 130 | + data.selectedTag = tagData.selectedTag; |
| 131 | + data.selectedTags = tagData.selectedTags; |
| 132 | + |
| 133 | + // RSS via helper |
| 134 | + setRssFields({ data, url, req, rssToken }); |
| 135 | + |
| 136 | + // Filters (defend if helpers.buildFilters returns non-array) |
| 137 | + data.filters = helpers.buildFilters ? helpers.buildFilters(baseUrl, activeFilter, query) : []; |
| 138 | + data.selectedFilter = Array.isArray(data.filters) |
| 139 | + ? data.filters.find(f => f && f.selected) || null |
| 140 | + : null; |
| 141 | + |
| 142 | + return data; |
116 | 143 | }; |
117 | 144 |
|
118 | 145 | require('../promisify')(recentController, ['get']); |
119 | | - |
|
0 commit comments