Skip to content

Commit

Permalink
fix(Commons): handle custom catalogs
Browse files Browse the repository at this point in the history
  • Loading branch information
jakeaturner committed Feb 6, 2024
1 parent e09cfc1 commit 89b6809
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 40 deletions.
180 changes: 152 additions & 28 deletions server/api/books.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import PeerReview from "../models/peerreview.js";
import Tag from "../models/tag.js";
import CIDDescriptor from "../models/ciddescriptor.js";
import conductorErrors from "../conductor-errors.js";
import { getSubdomainFromUrl,
import {
getSubdomainFromUrl,
getPaginationOffset,
isEmptyString,
isValidDateObject, sleep,
isValidDateObject,
sleep,
getRandomOffset,
} from "../util/helpers.js";
import {
checkBookIDFormat,
Expand Down Expand Up @@ -745,45 +748,161 @@ async function getCommonsCatalog(
) {
try {
const orgID = process.env.ORG_ID;
const activePage = req.query.activePage ? parseInt(req.query.activePage) : 1;
const limit = req.query.limit ? parseInt(req.query.limit) : 10;
const offset = getPaginationOffset(activePage, limit);
const activePage = req.query.activePage
? parseInt(req.query.activePage.toString())
: 1;
const limit = req.query.limit ? parseInt(req.query.limit.toString()) : 10;

let sortObj = {};
if(req.query.sort && req.query.sort === 'author'){
if (req.query.sort && req.query.sort === "author") {
sortObj = {
author: 1
author: 1,
};
}
if(req.query.sort && req.query.sort === 'title'){
}
if (req.query.sort && req.query.sort === "title") {
sortObj = {
title: 1
title: 1,
};
}

const searchQueries = [];

// Find books associated with projects
const projectWithAssociatedBookQuery = {
$expr: {
$and: [
{ $eq: ["$orgID", orgID] },
{ $ne: [{ $type: "$libreLibrary" }, "missing"] },
{ $gt: [{ $strLenBytes: "$libreLibrary" }, 0] },
{ $ne: [{ $type: "$libreCoverID" }, "missing"] },
{ $gt: [{ $strLenBytes: "$libreCoverID" }, 0] },
],
},
};
const projectWithAssociatedBookProjection = {
_id: 0,
libreLibrary: 1,
libreCoverID: 1,
};

const projResults = await Project.aggregate([
{
$match: projectWithAssociatedBookQuery,
},
{
$project: projectWithAssociatedBookProjection,
},
]);

if (!Array.isArray(projResults) || projResults.length === 0) {
return res.send({
err: false,
numFound: 0,
numTotal: 0,
books: [],
});
}

const projBookIDs = projResults.map(
(proj) => `${proj.libreLibrary}-${proj.libreCoverID}`
);
const idMatchObj = { bookID: { $in: projBookIDs } };

const pipeline: PipelineStage[] = [
{ $match: {} },
{
$match: idMatchObj,
},
{ $project: BOOK_PROJECTION },
]
];

if(Object.keys(sortObj).length > 0){
if (Object.keys(sortObj).length > 0) {
pipeline.push({ $sort: sortObj });
}

const aggResults = await Book.aggregate(pipeline).skip(offset).limit(limit);
searchQueries.push(Book.aggregate(pipeline));

// Find books in org's custom catalog
if (orgID !== "libretexts") {
const orgData = await Organization.findOne(
{ orgID },
{
_id: 0,
orgID: 1,
name: 1,
shortName: 1,
abbreviation: 1,
aliases: 1,
catalogMatchingTags: 1,
}
).lean();

const customCatalog = await CustomCatalog.findOne(
{ orgID },
{
_id: 0,
orgID: 1,
resources: 1,
}
).lean();

if (orgData) {
const hasCustomEntries =
customCatalog &&
Array.isArray(customCatalog.resources) &&
customCatalog.resources.length > 0;
const hasCatalogMatchingTags =
Array.isArray(orgData?.catalogMatchingTags) &&
orgData?.catalogMatchingTags.length > 0;

if (hasCustomEntries || hasCatalogMatchingTags) {
let searchAreaObj = {};
const idMatchObj = { bookID: { $in: customCatalog?.resources } };
const tagMatchObj = {
libraryTags: { $in: orgData.catalogMatchingTags },
};
if (hasCustomEntries && hasCatalogMatchingTags) {
searchAreaObj = { $or: [idMatchObj, tagMatchObj] };
} else if (hasCustomEntries) {
searchAreaObj = idMatchObj;
} else {
searchAreaObj = tagMatchObj;
}

searchQueries.push(
Book.aggregate([
{ $match: searchAreaObj },
{ $project: BOOK_PROJECTION },
])
);
}
}
}

const results = await Promise.all(searchQueries);
const totalNumBooks = await Book.estimatedDocumentCount();

const aggResults = results.reduce((acc, curr) => {
if (Array.isArray(curr)) {
return acc.concat(curr);
}
return acc;
}, []);

// Ensure no duplicates
const resultBookIDs = [...new Set(aggResults.map((book) => book.bookID))];
const resultBooks = [
...aggResults.filter((book) => resultBookIDs.includes(book.bookID)),
];
]

const offset = getRandomOffset(resultBooks.length)

const randomized = resultBooks.slice(offset, offset + limit)

return res.send({
err: false,
numFound: resultBooks.length,
numTotal: totalNumBooks,
books: resultBooks,
books: randomized,
});
} catch (e) {
debugError(e);
Expand Down Expand Up @@ -1117,23 +1236,27 @@ async function createBook(
const project = await Project.findOne({ projectID }).orFail();

const libraryApp = await centralIdentity.getApplicationById(library);
if(!libraryApp) {
if (!libraryApp) {
throw new Error("badlibrary");
}

const subdomain = getSubdomainFromUrl(libraryApp.main_url);
if(!subdomain) {
if (!subdomain) {
throw new Error("badlibrary");
}

// Check project permissions
const canCreate = projectsAPI.checkProjectMemberPermission(project, user)
if(!canCreate) {
const canCreate = projectsAPI.checkProjectMemberPermission(project, user);
if (!canCreate) {
throw new Error(conductorErrors.err8);
}

const hasLibAccess = await centralIdentity.checkUserApplicationAccessInternal(user.centralID, libraryApp.id);
if(!hasLibAccess) {
const hasLibAccess =
await centralIdentity.checkUserApplicationAccessInternal(
user.centralID,
libraryApp.id
);
if (!hasLibAccess) {
throw new Error(conductorErrors.err8);
}

Expand All @@ -1148,7 +1271,7 @@ async function createBook(
method: "POST",
body: MindTouch.Templates.POST_CreateBook,
},
query: { abort: 'exists'}
query: { abort: "exists" },
}).catch((e) => {
const err = new Error(conductorErrors.err86);
err.name = "CreateBookError";
Expand Down Expand Up @@ -1244,26 +1367,27 @@ async function createBook(
newBookID
);

if(!permsUpdated) {
console.log(`[createBook] Failed to update permissions for ${projectID}.`) // Silent fail
if (!permsUpdated) {
console.log(
`[createBook] Failed to update permissions for ${projectID}.`
); // Silent fail
}


console.log(`[createBook] Created ${bookPath}.`);
return res.send({
err: false,
path: bookPath,
url: bookURL,
});
} catch (err: any) {
if(err.name === "DocumentNotFoundError" || err.name === "badlibrary") {
if (err.name === "DocumentNotFoundError" || err.name === "badlibrary") {
return res.status(404).send({
err: true,
errMsg: conductorErrors.err11,
});
}
debugError(err);
if(["CreateBookError", 'badlibrary'].includes(err.name)) {
if (["CreateBookError", "badlibrary"].includes(err.name)) {
return res.status(400).send({
err: true,
errMsg: err.message,
Expand Down
19 changes: 7 additions & 12 deletions server/api/projectfiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
} from "../util/projectutils.js";
import { isObjectIdOrHexString } from "mongoose";
import async from "async";
import { assembleUrl, getPaginationOffset } from "../util/helpers.js";
import { assembleUrl, getPaginationOffset, getRandomOffset } from "../util/helpers.js";
import {
CopyObjectCommand,
DeleteObjectCommand,
Expand Down Expand Up @@ -1017,7 +1017,6 @@ async function getPublicProjectFiles(
try {
const page = parseInt(req.query.page.toString()) || 1;
const limit = parseInt(req.query.limit.toString()) || 24;
const offset = getPaginationOffset(page, limit);

const aggRes = await ProjectFile.aggregate([
{
Expand All @@ -1038,6 +1037,7 @@ async function getPublicProjectFiles(
$eq: ["$projectID", "$$searchID"],
},
visibility: "public",
orgID: process.env.ORG_ID,
},
},
{
Expand Down Expand Up @@ -1168,21 +1168,16 @@ async function getPublicProjectFiles(
_id: -1,
},
},
{
$skip: offset,
},
{
$limit: limit,
},
]);

const totalCount = await ProjectFile.countDocuments({
access: "public",
});
const totalCount = aggRes.length;
const offset = getRandomOffset(totalCount);

const paginatedRes = aggRes.slice(offset, offset + limit);

return res.send({
err: false,
files: aggRes || [],
files: paginatedRes || [],
totalCount: totalCount || 0,
});
} catch (e) {
Expand Down
1 change: 1 addition & 0 deletions server/api/projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -1343,6 +1343,7 @@ async function getPublicProjects(req, res) {
const offset = getPaginationOffset(page, limit);

const projects = await Project.find({
orgID: process.env.ORG_ID,
visibility: "public",
}).select({
notes: 0, leads: 0 , liaisons: 0, members: 0, auditors: 0, a11yReview: 0, flag: 0, flagDescrip: 0
Expand Down
9 changes: 9 additions & 0 deletions server/util/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,15 @@ export function getPaginationOffset(page, offsetMultiplier = 25) {
return offset;
}

/**
* Generates a random number between 0 and max
* @param {Number} max - The maximum number to generate a random offset for
* @returns {Number} - A random number between 0 and max
*/
export function getRandomOffset(max){
return Math.floor(Math.random() * max);
}

/**
* Breaks up a url into the subdomain and path
* @param {string} url
Expand Down

0 comments on commit 89b6809

Please sign in to comment.