From 926234710e6bb86159c617320aeee5c03bf29a7d Mon Sep 17 00:00:00 2001 From: dannyirwin Date: Tue, 12 Oct 2021 12:35:34 -0600 Subject: [PATCH 1/5] resource filter moved to resource endpoint --- .../server/src/routes/api/resources/index.ts | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/server/src/routes/api/resources/index.ts b/packages/server/src/routes/api/resources/index.ts index a72b593b4..639f4e8b7 100644 --- a/packages/server/src/routes/api/resources/index.ts +++ b/packages/server/src/routes/api/resources/index.ts @@ -1,13 +1,53 @@ import Resource, { resourceDocumentToResource } from "../../../models/Resource"; +import Category from "../../../models/Category"; import { ObjectId } from "bson"; +import { TCategory } from "@upswyng/types"; +import { TResource } from "@upswyng/types"; +import { TSubcategory } from "@upswyng/types"; export async function get(_req, res, _next) { try { const idParam = _req.query.id; - const resourceIds = idParam?.split(",")?.map(ObjectId.createFromHexString); - const resourceDocuments = resourceIds - ? await Resource.getByResourceIds(resourceIds) - : await Resource.getAll(); + const resourceIds = idParam?.split(",") || []; + const categoryFilters = _req.query?.categories?.split(",") || []; + + const getCategories = async () => { + return Promise.all( + categoryFilters.map(async (categoryName: string) => { + const stub = categoryName.toLowerCase().replace(/\s/, "_"); //TODO: how will two word query params work? + return await Category.getByStub(stub); + }) + ); + }; + + const categories = await getCategories(); + const resourceIdsFromCategories = [ + ...new Set( + categories + .map((category: TCategory) => { + return category?.subcategories.map((subcategory: TSubcategory) => { + return subcategory?.resources.map((resource: TResource) => + resource.resourceId.toString() + ); + }); + }) + .flat(2) + ), + ]; + + const allIds = + categories.length > 0 && resourceIds.length > 0 + ? resourceIds.filter((id: string) => { + return resourceIdsFromCategories.includes(id); + }) + : [...new Set([...resourceIds, ...resourceIdsFromCategories])]; + const allResourceIds = allIds.map(ObjectId.createFromHexString); + + const resourceDocuments = + allResourceIds.length > 0 + ? await Resource.getByResourceIds(allResourceIds) + : await Resource.getAll(); + const resources = await Promise.all( resourceDocuments.map(resourceDocumentToResource) ); From 1ec17b9a1632eab6773e3e4cab387e6e76fab3a8 Mon Sep 17 00:00:00 2001 From: dannyirwin Date: Wed, 13 Oct 2021 11:39:46 -0600 Subject: [PATCH 2/5] frontend consumes new /resources/ endpoint --- .../src/components/useResourcesByCategory.tsx | 61 +++++-------------- 1 file changed, 14 insertions(+), 47 deletions(-) diff --git a/packages/web/src/components/useResourcesByCategory.tsx b/packages/web/src/components/useResourcesByCategory.tsx index 7562844fd..f4da6e6fa 100644 --- a/packages/web/src/components/useResourcesByCategory.tsx +++ b/packages/web/src/components/useResourcesByCategory.tsx @@ -1,65 +1,32 @@ import { TResource } from "@upswyng/types"; -import { TResourcesByCategoryPayload } from "../webTypes"; +import { TResourcePayload } from "../webTypes"; import apiClient from "../utils/apiClient"; import { useQuery } from "react-query"; const getResourcesByCategory = async ( _queryKey: string, params: { category: string } -): Promise => { - const { data } = await apiClient.get( - `/category/${params.category}` +): Promise => { + const { data } = await apiClient.get( + `/resources/?categories=${params.category}` ); - if (!data.category) { + if (!data.resources) { throw new Error("no category found in resources by category response"); } - const { - category: { subcategories }, - } = data; - if (!(subcategories || []).length) { - throw new Error( - "no sub-categories found in resources by category response" - ); - } + // const { + // category: { subcategories }, + // } = data; + // if (!(subcategories || []).length) { + // throw new Error( + // "no sub-categories found in resources by category response" + // ); + // } return data; }; -const getUniqueFlattenedResources = ( - payload?: TResourcesByCategoryPayload -): TResource[] | undefined => { - const subcategories = payload?.category?.subcategories; - if (!subcategories || !subcategories.length) { - return; - } - - const uniqueResources = subcategories.reduce( - (categoryResources, subcategory) => { - const { resources: subcategoryResources } = subcategory; - if (!subcategoryResources || !subcategoryResources.length) { - return categoryResources; - } - - const uniqueSubcategoryResources = categoryResources.length - ? subcategoryResources.filter( - resource => - !categoryResources.find( - categoryResource => - categoryResource.resourceId === resource.resourceId - ) - ) - : subcategoryResources; - - return categoryResources.concat(uniqueSubcategoryResources); - }, - [] - ); - - return uniqueResources; -}; - const useResourcesByCategory = ( categoryStub?: string ): { @@ -73,7 +40,7 @@ const useResourcesByCategory = ( staleTime: 900000, // 15 min } ); - const resources = getUniqueFlattenedResources(data); + const resources = data?.resources; return { data: resources, status }; }; From adcf2d2061a79fb46201de38322fb41dd1a8f208 Mon Sep 17 00:00:00 2001 From: dannyirwin Date: Thu, 14 Oct 2021 11:01:40 -0600 Subject: [PATCH 3/5] remove unneeded stub parsing from getCategories --- packages/server/src/routes/api/resources/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/server/src/routes/api/resources/index.ts b/packages/server/src/routes/api/resources/index.ts index 639f4e8b7..c11584e8b 100644 --- a/packages/server/src/routes/api/resources/index.ts +++ b/packages/server/src/routes/api/resources/index.ts @@ -13,8 +13,7 @@ export async function get(_req, res, _next) { const getCategories = async () => { return Promise.all( - categoryFilters.map(async (categoryName: string) => { - const stub = categoryName.toLowerCase().replace(/\s/, "_"); //TODO: how will two word query params work? + categoryFilters.map(async (stub: string) => { return await Category.getByStub(stub); }) ); From 20a345c701e880551cd1e2f3508989284e557d2a Mon Sep 17 00:00:00 2001 From: dannyirwin Date: Sat, 23 Oct 2021 11:40:01 -0600 Subject: [PATCH 4/5] add Category.getByStubs --- packages/server/src/models/Category.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/server/src/models/Category.ts b/packages/server/src/models/Category.ts index c182d2d4d..3e9355e41 100644 --- a/packages/server/src/models/Category.ts +++ b/packages/server/src/models/Category.ts @@ -110,10 +110,19 @@ CategorySchema.statics.getByStub = async function( return result; }; +/* Finds and returns all categories from an array of category stubs.*/ +CategorySchema.statics.getByStubs = async function( + stubs: string[] +): Promise { + const result = await this.find({ stub: { $in: stubs } }); + return result; +}; + /** * Creates or finds an existing subcategory by its name and adds * it as a child of this category */ + (CategorySchema.methods as any).addSubcategory = async function( subcategoryName: string, subcategoryStub: string @@ -138,4 +147,5 @@ export default Category as typeof Category & { ) => Promise; getCategoryList: () => Promise; getByStub: (stub: string) => Promise; + getByStubs: (stubs: string[]) => Promise; }; From 1a78ca37d4e1a3552acaf42ac1cfecbe87f63465 Mon Sep 17 00:00:00 2001 From: dannyirwin Date: Sat, 23 Oct 2021 13:42:05 -0600 Subject: [PATCH 5/5] add Resource.getBySubcategoryIds and implamented it on the resources route --- packages/server/src/models/Category.ts | 2 +- packages/server/src/models/Resource.ts | 41 +++++++++++++ .../server/src/routes/api/resources/index.ts | 58 ++++++------------- 3 files changed, 61 insertions(+), 40 deletions(-) diff --git a/packages/server/src/models/Category.ts b/packages/server/src/models/Category.ts index 3e9355e41..fa5190e5d 100644 --- a/packages/server/src/models/Category.ts +++ b/packages/server/src/models/Category.ts @@ -32,7 +32,7 @@ export async function categoryDocumentToCategory( // ); if (d.hasOwnProperty("_bsontype")) { // console.warn("This appears to be an ObjectId"); - // console.trace(); + // cons ole.trace(); return null; } } diff --git a/packages/server/src/models/Resource.ts b/packages/server/src/models/Resource.ts index 20e7e1d70..9658fd559 100644 --- a/packages/server/src/models/Resource.ts +++ b/packages/server/src/models/Resource.ts @@ -365,6 +365,43 @@ ResourceSchema.statics.getByResourceIds = async function( .populate("lastModifiedBy"); }; +/** + * Retrieve all resources from an array of category stubs. `null` if there is no matching Resources. + * Can also optionally specify specific resources to filter using an array of resourceIds. + * The `includeDeleted` flag must be set to `true` to return trashed resources. + */ +ResourceSchema.statics.getBySubcategoryIds = async function( + subcategoryIds: ObjectId[], + resourceIds: ObjectId[] = null, + includeDeleted: boolean = false +): Promise { + const resourcesFromSubcategories = await this.find({ + subcategories: { $in: subcategoryIds }, + deleted: { $in: [false, includeDeleted] }, + }); + + const resources = resourceIds + ? resourcesFromSubcategories.filter( + (subcategoryResource: TResourceDocument) => { + const id = subcategoryResource.resourceId; + // console.log(id, resourceIds[0], id in { resourceIds }); + + return resourceIds.reduce( + (isIncluded: boolean, resourceId: ObjectId) => { + if (resourceId == id) { + isIncluded = true; + } + return isIncluded; + }, + false + ); + } + ) + : resourcesFromSubcategories; + + return resources; +}; + /** * Retrieve all resources. */ @@ -431,6 +468,10 @@ export default Resource as typeof Resource & { getByResourceIds: (resourceIds: ObjectId[]) => Promise; getByResourceId: (resourceId: ObjectId) => Promise; getUncategorized: () => Promise; + getBySubcategoryIds: ( + subCategoryIds: ObjectId[], + resourceIds?: ObjectId[] + ) => Promise; }; const DraftResource = mongoose.model( diff --git a/packages/server/src/routes/api/resources/index.ts b/packages/server/src/routes/api/resources/index.ts index c11584e8b..f987a56cd 100644 --- a/packages/server/src/routes/api/resources/index.ts +++ b/packages/server/src/routes/api/resources/index.ts @@ -1,51 +1,31 @@ import Resource, { resourceDocumentToResource } from "../../../models/Resource"; import Category from "../../../models/Category"; import { ObjectId } from "bson"; -import { TCategory } from "@upswyng/types"; -import { TResource } from "@upswyng/types"; -import { TSubcategory } from "@upswyng/types"; + +import { TCategoryDocument } from "../../../models/Category"; export async function get(_req, res, _next) { try { const idParam = _req.query.id; - const resourceIds = idParam?.split(",") || []; - const categoryFilters = _req.query?.categories?.split(",") || []; - - const getCategories = async () => { - return Promise.all( - categoryFilters.map(async (stub: string) => { - return await Category.getByStub(stub); - }) - ); - }; + const resourceIds = idParam?.split(","); - const categories = await getCategories(); - const resourceIdsFromCategories = [ - ...new Set( - categories - .map((category: TCategory) => { - return category?.subcategories.map((subcategory: TSubcategory) => { - return subcategory?.resources.map((resource: TResource) => - resource.resourceId.toString() - ); - }); - }) - .flat(2) - ), - ]; + const categoryStubs = _req.query?.categories?.split(","); + const categoryDocuments = await Category.getByStubs(categoryStubs); - const allIds = - categories.length > 0 && resourceIds.length > 0 - ? resourceIds.filter((id: string) => { - return resourceIdsFromCategories.includes(id); - }) - : [...new Set([...resourceIds, ...resourceIdsFromCategories])]; - const allResourceIds = allIds.map(ObjectId.createFromHexString); - - const resourceDocuments = - allResourceIds.length > 0 - ? await Resource.getByResourceIds(allResourceIds) - : await Resource.getAll(); + const subcategoryIds = categoryDocuments + .map((categoryDocument: TCategoryDocument) => { + return categoryDocument.subcategories; + }) + .flat(1); + + const resourceDocuments = categoryStubs + ? await Resource.getBySubcategoryIds( + subcategoryIds as ObjectId[], + resourceIds + ) + : resourceIds + ? await Resource.getByResourceIds(resourceIds) + : await Resource.getAll(); const resources = await Promise.all( resourceDocuments.map(resourceDocumentToResource)