Add collections (#172)

This commit is contained in:
Fredrik Ringqvist
2025-04-29 14:00:35 +02:00
committed by GitHub
parent efab961a29
commit 10fa25b230
7 changed files with 164 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
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
FROM editorial_pages
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
FROM editorial_pages
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 {
let productIds: number[] = [];
if (row.metadata && Array.isArray(row.metadata.collection_products)) {
productIds = row.metadata.collection_products.map((id: string | number) =>
Number(id),
);
}
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 productIdsStr = productIds.map(String);
const query = `
WITH collection_products AS (
SELECT
e.id,
e.name,
e.metadata,
jsonb_array_elements_text(e.metadata->'collection_products') AS product_id
FROM editorial_pages e
WHERE e.type = 'collection'
AND e.deleted IS NULL
AND e.metadata->'collection_products' IS NOT NULL
)
SELECT * FROM collection_products
WHERE product_id = ANY (?)
`;
const results = await this.knex.raw(query, [productIdsStr]);
// Group collections by product ID
const collectionsByProduct: { [key: string]: Collection[] } = {};
productIdsStr.forEach((id) => {
collectionsByProduct[id] = [];
});
results.rows.forEach((row) => {
const productId = row.product_id;
if (!collectionsByProduct[productId])
collectionsByProduct[productId] = [];
collectionsByProduct[productId].push(this.createCollectionFromRow(row));
});
// Return collections in the same order as the input productIds
return productIdsStr.map((id) => collectionsByProduct[id] || []);
}
}