Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"test:e2e": "dotenv -- turbo run test:e2e",
"prepare": "husky",
"format": "pnpm prettier --write '**/*.{js,ts,tsx,json,md}'",
"format:check": "pnpm prettier --check '**/*.{js,ts,tsx,json,md}'"
"format:check": "pnpm prettier --check '**/*.{js,ts,tsx,json,md}'",
"seed": "dotenv -- pnpm --filter=@nimara/seeder seed"
},
"devDependencies": {
"commitizen": "4.3.1",
Expand Down
18 changes: 18 additions & 0 deletions packages/seeder/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@nimara/seeder",
"version": "1.0.0",
"private": true,
"scripts": {
"seed": "tsx src/index.ts"
},
"dependencies": {
"dotenv": "^16.4.7",
"graphql": "^16.12.0",
"graphql-request": "^7.1.2"
},
"devDependencies": {
"@types/node": "^22.19.7",
"tsx": "^4.19.2",
"typescript": "^5.7.3"
}
}
53 changes: 53 additions & 0 deletions packages/seeder/src/actions/create-categories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { client } from "../client";
import { CATEGORY_CREATE_MUTATION } from "../mutations";
import { CategoryCreateResponse, CategoryMock } from "../types";

/**
* Creates categories recursively.
* @param categories - Array of category objects.
* @returns Map of category names to category ids.
*/
export async function createCategories(
categories: CategoryMock[],
): Promise<Record<string, string>> {
console.log("[SEEDING] Creating categories...");
const mapping: Record<string, string> = {};

async function processCategory(cat: CategoryMock, parentId?: string) {
const res = await client.request<CategoryCreateResponse>(
CATEGORY_CREATE_MUTATION,
{
input: { name: cat.name, slug: cat.slug },
parent: parentId,
},
);

if (res.categoryCreate.errors.length > 0) {
throw new Error(
`[SEEDING] Failed to create category ${cat.name}: ${JSON.stringify(
res.categoryCreate.errors,
)}`,
);
}

const categoryId = res.categoryCreate.category!.id;
mapping[cat.name] = categoryId;
console.log(
`[SEEDING] Created category: ${cat.name}${
parentId ? ` (parent: ${parentId})` : ""
}`,
);

if (cat.children && cat.children.length > 0) {
for (const child of cat.children) {
await processCategory(child, categoryId);
}
}
}

for (const cat of categories) {
await processCategory(cat);
}

return mapping;
}
44 changes: 44 additions & 0 deletions packages/seeder/src/actions/create-homepage-attributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { client } from "../client";
import { HOMEPAGE_ATTRIBUTES } from "../constants";
import { ATTRIBUTE_BULK_CREATE_MUTATION } from "../mutations";
import { AttributeBulkCreateResponse } from "../types";

/**
* Creates homepage attributes.
* @returns Map of attribute slugs to attribute ids.
*/
export async function createHomepageAttributes(): Promise<
Record<string, string>
> {
console.log("[SEEDING] Creating homepage attributes (bulk)...");

const inputs = HOMEPAGE_ATTRIBUTES.map((attr) => ({
name: attr.name,
type: attr.type,
inputType: attr.inputType,
entityType: attr.entityType || null,
values: attr.values || [],
}));

const res = await client.request<AttributeBulkCreateResponse>(
ATTRIBUTE_BULK_CREATE_MUTATION,
{ input: inputs },
);

const attrIdsBySlug: Record<string, string> = {};

res.attributeBulkCreate.results.forEach((result, index) => {
if (result.errors && result.errors.length > 0) {
console.error(
`[SEEDING] Failed to create attribute ${HOMEPAGE_ATTRIBUTES[index].name}: ${JSON.stringify(result.errors)}`,
);
} else if (result.attribute) {
attrIdsBySlug[HOMEPAGE_ATTRIBUTES[index].slug] = result.attribute.id;
console.log(
`[SEEDING] Created attribute: ${result.attribute.name} (${result.attribute.id})`,
);
}
});

return attrIdsBySlug;
}
54 changes: 54 additions & 0 deletions packages/seeder/src/actions/create-homepage-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { client } from "../client";
import { buildPageAttributes } from "../helpers";
import { PAGE_CREATE_MUTATION } from "../mutations";
import { HomepageMock } from "../types";
import { uploadPageFileAttributesGeneric } from "./upload-page-file-attributes-generic";

/**
* Creates homepage page.
* @param pageTypeId - Id of the page type.
* @param attrIdsBySlug - Map of attribute slugs to attribute ids.
* @param productIds - Array of product ids.
* @param homepage - Homepage data.
*/
export async function createHomepagePage(
pageTypeId: string,
attrIdsBySlug: Record<string, string>,
productIds: string[],
homepage: HomepageMock,
): Promise<void> {
console.log("[SEEDING] Creating homepage page...");

const { attributes, fileUploads } = buildPageAttributes(
attrIdsBySlug,
homepage,
productIds,
);

const pageRes = await client.request(PAGE_CREATE_MUTATION, {
input: {
pageType: pageTypeId,
title: "Homepage",
slug: "home",
isPublished: true,
attributes,
},
});

if (pageRes.pageCreate.errors?.length > 0) {
console.error(
"[SEEDING] Failed to create homepage:",
pageRes.pageCreate.errors,
);
return;
}

const pageId = pageRes.pageCreate.page?.id;
console.log(`[SEEDING] Created homepage page (${pageId})`);

if (fileUploads.length > 0) {
await uploadPageFileAttributesGeneric(pageId!, fileUploads);
}

console.log("[SEEDING] Homepage setup complete");
}
104 changes: 104 additions & 0 deletions packages/seeder/src/actions/create-media-for-all-products.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { ProductBulkCreateResponse, ProductMock } from "../types";

const SALEOR_API_URL = process.env.NEXT_PUBLIC_SALEOR_API_URL;
const SALEOR_APP_TOKEN = process.env.SALEOR_APP_TOKEN;

export async function createMediaForAllProducts(
results: ProductBulkCreateResponse["productBulkCreate"]["results"],
seedProducts: ProductMock[],
): Promise<void> {
console.log("[SEEDING] Creating media for all products...");

const tasks = results
.map((result, i) => ({
product: result.product,
photoUrl: seedProducts[i].photoUrl,
index: i,
}))
.filter(
(
t,
): t is {
product: NonNullable<typeof t.product>;
photoUrl: string;
index: number;
} => t.product != null,
);

const promises = tasks.map(async ({ product, photoUrl, index }) => {
try {
console.log(`[SEEDING] Downloading image for ${product.name}...`);
const imageRes = await fetch(photoUrl);
const buffer = Buffer.from(await imageRes.arrayBuffer());
const file = new File([buffer], `cover-${index}.jpg`, {
type: "image/jpeg",
});

const query = `
mutation ProductMediaCreate($productId: ID!, $file: Upload!) {
productMediaCreate(
input: {
product: $productId
image: $file
alt: "${product.name} cover"
}
) {
media {
id
url
}
errors {
field
message
}
}
}
`;

const operations = JSON.stringify({
query,
variables: {
productId: product.id,
file: null,
},
});

const map = JSON.stringify({
0: ["variables.file"],
});

const formData = new FormData();
formData.append("operations", operations);
formData.append("map", map);
formData.append("0", file);

const response = await fetch(SALEOR_API_URL!, {
method: "POST",
headers: {
Authorization: `Bearer ${SALEOR_APP_TOKEN}`,
},
body: formData,
});

const res = await response.json();
const errors = res.data?.productMediaCreate?.errors;

if (errors?.length > 0) {
console.error(
`[SEEDING] Failed to create media for ${product.name}: ${JSON.stringify(errors)}`,
);
} else if (res.data?.productMediaCreate?.media) {
console.log(
`[SEEDING] Added media to product: ${product.name} (${product.id})`,
);
}
} catch (error) {
console.error(
`[SEEDING] Request failed for product ${product.name} (${product.id})`,
error,
);
}
});

await Promise.allSettled(promises);
}
43 changes: 43 additions & 0 deletions packages/seeder/src/actions/create-menu-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { client } from "../client";
import { MENU_ITEM_CREATE_MUTATION } from "../mutations";
import { MenuItemCreateResponse } from "../types";

/**
* Creates a menu item.
* @param menuId - Id of the menu.
* @param name - Name of the menu item.
* @param link - Link to the menu item.
* @returns Id of the created menu item.
*/
export async function createMenuItem(
menuId: string,
name: string,
link: { categoryId?: string; pageId?: string; url?: string; parent?: string },
): Promise<string> {
console.log(`[SEEDING] Creating menu item: ${name}...`);
const res = await client.request<MenuItemCreateResponse>(
MENU_ITEM_CREATE_MUTATION,
{
input: {
menu: menuId,
name,
category: link.categoryId,
page: link.pageId,
url: link.url,
parent: link.parent,
},
},
);

if (res.menuItemCreate.errors.length > 0) {
throw new Error(
`[SEEDING] Failed to create menu item ${name}: ${JSON.stringify(
res.menuItemCreate.errors,
)}`,
);
}

const id = res.menuItemCreate.menuItem!.id;
console.log(`[SEEDING] Created menu item: ${name} (${id})`);
return id;
}
25 changes: 25 additions & 0 deletions packages/seeder/src/actions/create-menu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { client } from "../client";
import { MENU_CREATE_MUTATION } from "../mutations";
import { MenuCreateResponse } from "../types";

/**
* Creates a menu.
* @param name - Name of the menu.
* @returns Id of the created menu.
*/
export async function createMenu(name: string): Promise<string> {
console.log(`[SEEDING] Creating menu: ${name}...`);
const res = await client.request<MenuCreateResponse>(MENU_CREATE_MUTATION, {
input: { name },
});

if (res.menuCreate.errors.length > 0) {
throw new Error(
`[SEEDING] Failed to create menu ${name}: ${JSON.stringify(res.menuCreate.errors)}`,
);
}

const id = res.menuCreate.menu!.id;
console.log(`[SEEDING] Created menu: ${name} (${id})`);
return id;
}
37 changes: 37 additions & 0 deletions packages/seeder/src/actions/create-page-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { client } from "../client";
import { PAGE_TYPE_CREATE_MUTATION } from "../mutations";
import { PageTypeCreateResponse } from "../types";

/**
* Creates a page type.
* @param name - Name of the page type.
* @param attributeIds - Array of attribute ids.
* @returns Id of the created page type.
*/
export async function createPageType(
name: string,
attributeIds: string[] = [],
): Promise<string> {
console.log(`[SEEDING] Creating Page Type: ${name}...`);
const res = await client.request<PageTypeCreateResponse>(
PAGE_TYPE_CREATE_MUTATION,
{
input: {
name,
addAttributes: attributeIds.length > 0 ? attributeIds : undefined,
},
},
);

if (res.pageTypeCreate.errors.length > 0) {
throw new Error(
`[SEEDING] Failed to create Page Type ${name}: ${JSON.stringify(
res.pageTypeCreate.errors,
)}`,
);
}

const id = res.pageTypeCreate.pageType!.id;
console.log(`[SEEDING] Created Page Type: ${name} (${id})`);
return id;
}
Loading