* fetch collections from new table * optimization fixes * fixed fields
141 lines
4.2 KiB
TypeScript
141 lines
4.2 KiB
TypeScript
import { BaseSQLDataSource } from './BaseSQLDataSource';
|
|
import { DataSourceOptions, Maybe } from '../types/types';
|
|
import { ScopeAccess, Scopes } from '../cognito/access-control';
|
|
import { Collection } from '../types/collection-types';
|
|
import DataLoader from 'dataloader';
|
|
|
|
export class CollectionAPI extends BaseSQLDataSource {
|
|
productCollectionsLoader: DataLoader<number, Collection[]>;
|
|
|
|
constructor(options: DataSourceOptions, config) {
|
|
super(options, config);
|
|
|
|
// DataLoader for batching product-to-collections lookups
|
|
this.productCollectionsLoader = new DataLoader(
|
|
async (productIds: readonly number[]) => {
|
|
return this.batchGetProductCollections(productIds);
|
|
},
|
|
);
|
|
}
|
|
|
|
async getCollections(): Promise<Array<Collection>> {
|
|
ScopeAccess.validate(this.user).some([
|
|
Scopes.PRODUCTS_READ,
|
|
Scopes.PRODUCTS_PUBLIC_READ,
|
|
]);
|
|
const query = `
|
|
SELECT
|
|
id,
|
|
name,
|
|
metadata,
|
|
(SELECT array_agg(DISTINCT ecp.product_id)
|
|
FROM editorial_collection_products ecp
|
|
WHERE ecp.editorial_page_id = ep.id
|
|
) AS product_ids
|
|
FROM editorial_pages ep
|
|
WHERE type = 'collection' AND deleted IS NULL
|
|
ORDER BY name
|
|
`;
|
|
const collections = await this.knex.raw(query).then((data) => {
|
|
return data.rows.map((row) => this.createCollectionFromRow(row));
|
|
});
|
|
return collections;
|
|
}
|
|
|
|
async getCollection(id: number): Promise<Maybe<Collection>> {
|
|
ScopeAccess.validate(this.user).some([
|
|
Scopes.PRODUCTS_READ,
|
|
Scopes.PRODUCTS_PUBLIC_READ,
|
|
]);
|
|
const query = `
|
|
SELECT
|
|
id,
|
|
name,
|
|
metadata,
|
|
(SELECT array_agg(DISTINCT ecp.product_id)
|
|
FROM editorial_collection_products ecp
|
|
WHERE ecp.editorial_page_id = ep.id
|
|
) AS product_ids
|
|
FROM editorial_pages ep
|
|
WHERE id = ? AND type = 'collection' AND deleted IS NULL
|
|
`;
|
|
const collection = await this.knex.raw(query, [id]).then((data) => {
|
|
if (data.rows.length === 0) {
|
|
return null;
|
|
}
|
|
return this.createCollectionFromRow(data.rows[0]);
|
|
});
|
|
return collection;
|
|
}
|
|
|
|
createCollectionFromRow(row: any): Collection {
|
|
const productIds: number[] = Array.isArray(row.product_ids)
|
|
? row.product_ids
|
|
: [];
|
|
return {
|
|
id: row.id,
|
|
name: row.name,
|
|
productIds,
|
|
};
|
|
}
|
|
|
|
async getProductCollections(productId: number): Promise<Collection[]> {
|
|
ScopeAccess.validate(this.user).some([
|
|
Scopes.PRODUCTS_READ,
|
|
Scopes.PRODUCTS_PUBLIC_READ,
|
|
]);
|
|
return this.productCollectionsLoader.load(productId);
|
|
}
|
|
|
|
private async batchGetProductCollections(
|
|
productIds: readonly number[],
|
|
): Promise<Collection[][]> {
|
|
const productIdsInt = productIds.map(Number);
|
|
const query = `
|
|
WITH collection_products AS (
|
|
SELECT
|
|
e.id,
|
|
e.name,
|
|
e.metadata,
|
|
array_agg(DISTINCT ecp.product_id) AS product_ids
|
|
FROM editorial_pages e
|
|
JOIN editorial_collection_products ecp ON ecp.editorial_page_id = e.id
|
|
WHERE e.type = 'collection'
|
|
AND e.deleted IS NULL
|
|
GROUP BY e.id, e.name, e.metadata
|
|
)
|
|
SELECT
|
|
id,
|
|
name,
|
|
metadata,
|
|
product_ids
|
|
FROM collection_products
|
|
WHERE product_ids && ?::integer[];
|
|
`;
|
|
const results = await this.knex.raw(query, [productIdsInt]);
|
|
|
|
// Group collections by product ID
|
|
const collectionsByProduct: { [key: string]: Collection[] } = {};
|
|
productIdsInt.forEach((id) => {
|
|
collectionsByProduct[id] = [];
|
|
});
|
|
|
|
results.rows.forEach((row) => {
|
|
const collection = this.createCollectionFromRow(row);
|
|
// Add this collection to each product ID that it contains
|
|
const collectionProductIds = row.product_ids || [];
|
|
collectionProductIds.forEach((productId: number) => {
|
|
if (productIdsInt.includes(productId)) {
|
|
if (!collectionsByProduct[productId]) {
|
|
collectionsByProduct[productId] = [];
|
|
}
|
|
collectionsByProduct[productId].push(collection);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Return collections in the same order as the input productIds
|
|
return productIdsInt.map((id) => collectionsByProduct[id] || []);
|
|
}
|
|
}
|