Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SearchAlg code with fixed Linter issues #27

Merged
merged 3 commits into from
Oct 7, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 144 additions & 118 deletions src/api/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,129 +14,155 @@ const controllersHelpers = require('../controllers/helpers');

const searchApi = module.exports;

// Main function for handling category searches
searchApi.categories = async (caller, data) => {
// Placeholder arrays for category IDs (cids) and matched category IDs (matchedCids)
let cids = [];
let matchedCids = [];

// Default privilege to check if not provided
const privilege = data.privilege || 'topics:read';

// Setting up watch states (e.g., watching, tracking, etc.)
data.states = (data.states || ['watching', 'tracking', 'notwatching', 'ignoring']).map(
state => categories.watchStates[state]
);

// Default parent category ID (cid)
data.parentCid = parseInt(data.parentCid || 0, 10);

// Check if there is a search query
if (data.search) {
// If there is a search query, find matched categories based on the search
({ cids, matchedCids } = await findMatchedCids(caller.uid, data));
} else {
// If no search query, load all categories normally
cids = await loadCids(caller.uid, data.parentCid);
}

// Get visible categories based on user's privileges and states
const visibleCategories = await controllersHelpers.getVisibleCategories({
cids, // Category IDs to check visibility
uid: caller.uid, // User ID
states: data.states, // Watch states (e.g., watching, tracking)
privilege, // The privilege to check (default is 'topics:read')
showLinks: data.showLinks,
parentCid: data.parentCid,
});

// Handle selected categories from the UI if provided
if (Array.isArray(data.selectedCids)) {
data.selectedCids = data.selectedCids.map(cid => parseInt(cid, 10));
}

// Build the final category data array
let categoriesData = categories.buildForSelectCategories(visibleCategories, ['disabledClass'], data.parentCid);
categoriesData = categoriesData.slice(0, 200); // Limit to 200 categories

// Mark selected and matched categories in the result
categoriesData.forEach((category) => {
category.selected = data.selectedCids ? data.selectedCids.includes(category.cid) : false;
if (matchedCids.includes(category.cid)) {
category.match = true; // Mark matched categories
}
});

// Fire a plugin hook in case any other plugins want to modify the category data
const result = await plugins.hooks.fire('filter:categories.categorySearch', {
categories: categoriesData, // The category data
...data, // Spread in any additional data passed
uid: caller.uid, // The user ID
});

return { categories: result.categories }; // Return the final category result
// used by categorySearch module
let cids = [];
let matchedCids = [];
const privilege = data.privilege || 'topics:read';
data.states = (data.states || ['watching', 'tracking', 'notwatching', 'ignoring']).map(
state => categories.watchStates[state]
);
data.parentCid = parseInt(data.parentCid || 0, 10);
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 };
};

// Function to find matching categories based on the search query
async function findMatchedCids(uid, data) {
// Use the search function in the 'categories' module to search for categories
const result = await categories.search({
uid: uid, // User ID for permission checks
query: data.search, // The search query entered by the user
qs: data.query, // Additional query parameters
paginate: false, // No pagination for now
});

// Extract matching category IDs
let matchedCids = result.categories.map(c => c.cid);

// If not all watch states are selected, filter by watch state
const filterByWatchState = !Object.values(categories.watchStates)
.every(state => data.states.includes(state));

if (filterByWatchState) {
// Get watch states for the matched categories and filter based on those
const states = await categories.getWatchState(matchedCids, uid);
matchedCids = matchedCids.filter((cid, index) => data.states.includes(states[index]));
}

// Get the parent and child category IDs for each matched category
const rootCids = _.uniq(_.flatten(await Promise.all(matchedCids.map(categories.getParentCids))));
const allChildCids = _.uniq(_.flatten(await Promise.all(matchedCids.map(categories.getChildrenCids))));

// Return both the matched category IDs and the expanded list of categories
return {
cids: _.uniq(rootCids.concat(allChildCids).concat(matchedCids)), // Combined parent, child, and matched categories
matchedCids: matchedCids, // Only the matched categories
};
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,
};
}

// Function to load category IDs if no search query is provided
async function loadCids(uid, parentCid) {
let resultCids = [];

// Recursive function to gather child categories
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);
}
}));
}

// Get the root categories for the parent category
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;

// Recursively get child categories
await getCidsRecursive(pageCids);
return resultCids; // Return the list of category IDs
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;
}
searchApi.roomUsers = async (caller, { query, roomId }) => {
const [isAdmin, inRoom, isRoomOwner] = await Promise.all([
user.isAdministrator(caller.uid),
messaging.isUserInRoom(caller.uid, roomId),
messaging.isRoomOwner(caller.uid, roomId),
]);
if (!isAdmin && !inRoom) {
throw new Error('[[error:no-privileges]]');
}
const results = await user.search({
query,
paginate: false,
hardCap: -1,
uid: caller.uid,
});
const { users } = results;
const foundUids = users.map(user => user && user.uid);
const isUidInRoom = _.zipObject(
foundUids,
await messaging.isUsersInRoom(foundUids, roomId)
);
const roomUsers = users.filter(user => isUidInRoom[user.uid]);
const isOwners = await messaging.isRoomOwner(roomUsers.map(u => u.uid), roomId);
roomUsers.forEach((user, index) => {
if (user) {
user.isOwner = isOwners[index];
user.canKick = isRoomOwner && (parseInt(user.uid, 10) !== parseInt(caller.uid, 10));
}
});
roomUsers.sort((a, b) => {
if (a.isOwner && !b.isOwner) {
return -1;
} else if (!a.isOwner && b.isOwner) {
return 1;
}
return 0;
});
return { users: roomUsers };
};
searchApi.roomMessages = async (caller, { query, roomId, uid }) => {
const [roomData, inRoom] = await Promise.all([
messaging.getRoomData(roomId),
messaging.isUserInRoom(caller.uid, roomId),
]);
if (!roomData) {
throw new Error('[[error:no-room]]');
}
if (!inRoom) {
throw new Error('[[error:no-privileges]]');
}
const { ids } = await plugins.hooks.fire('filter:messaging.searchMessages', {
content: query,
roomId: [roomId],
uid: [uid],
matchWords: 'any',
ids: [],
});
let userjoinTimestamp = 0;
if (!roomData.public) {
userjoinTimestamp = await db.sortedSetScore(`chat:room:${roomId}:uids`, caller.uid);
}
let messageData = await messaging.getMessagesData(ids, caller.uid, roomId, false);
messageData = messageData
.map((msg) => {
if (msg) {
msg.newSet = true;
}
return msg;
})
.filter(msg => msg && !msg.deleted && msg.timestamp > userjoinTimestamp);
return { messages: messageData };
};