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

issue 488 #495

Closed
wants to merge 8 commits into from
Closed
47 changes: 43 additions & 4 deletions packages/server/src/routes/api/resources/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,52 @@
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 (stub: string) => {
return await Category.getByStub(stub);
jacobvenable marked this conversation as resolved.
Show resolved Hide resolved
})
);
};

const categories = await getCategories();
const resourceIdsFromCategories = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm conflicted about using the IDs that we receive from the category model. In the end, I think we'd rather query the resources model only once based on the provided filters.

So instead of:

  1. getting categories, which queries the DB for categories and then queries it again for resources IDs belonging to that category
  2. crawling resources for those having an ID that matches the provided id filter

We'd instead:

  1. get categories, which only queries the DB for categories
  2. get resources that is one query looking for resources that belong to that category and also have an ID in the provided id filter

What do you think about:

  1. creating a new method in the Category model called getByStubs which is similar to getByStub. This would accept an array of stubs and only include subcategories (without populating resources).
  2. adding a new method to the Resource model that accepts an array of sub categories and resource IDs and finds resources based on both parameters. It'd be similar to getByResourceIds with sub-categories added to the mix.

Thoughts?

...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)
);
Expand Down
61 changes: 14 additions & 47 deletions packages/web/src/components/useResourcesByCategory.tsx
Original file line number Diff line number Diff line change
@@ -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<TResourcesByCategoryPayload> => {
const { data } = await apiClient.get<TResourcesByCategoryPayload>(
`/category/${params.category}`
): Promise<TResourcePayload> => {
const { data } = await apiClient.get<TResourcePayload>(
`/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<TResource[]>(
(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
): {
Expand All @@ -73,7 +40,7 @@ const useResourcesByCategory = (
staleTime: 900000, // 15 min
}
);
const resources = getUniqueFlattenedResources(data);
const resources = data?.resources;

return { data: resources, status };
};
Expand Down