Skip to content

Commit

Permalink
Merge pull request #27 from CMU-313/searchalgfixes
Browse files Browse the repository at this point in the history
SearchAlg code with fixed Linter issues
  • Loading branch information
philliparaujo authored Oct 7, 2024
2 parents 0de0dac + 93c0479 commit e33ee67
Showing 1 changed file with 144 additions and 118 deletions.
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 };
};

0 comments on commit e33ee67

Please sign in to comment.