feat: categories seeding script
This commit is contained in:
@@ -14,7 +14,8 @@
|
|||||||
"types:generate": "openapi-typescript http://localhost:1337/swagger-spec.json -o ./src/api/paths.ts",
|
"types:generate": "openapi-typescript http://localhost:1337/swagger-spec.json -o ./src/api/paths.ts",
|
||||||
"graphql:generate": "graphql-codegen",
|
"graphql:generate": "graphql-codegen",
|
||||||
"graphql:schema": "graphql-codegen -p introspect",
|
"graphql:schema": "graphql-codegen -p introspect",
|
||||||
"endpoint:generate": "node scripts/endpoint-generate.mjs"
|
"endpoint:generate": "node scripts/endpoint-generate.mjs",
|
||||||
|
"seed:categories": "node scripts/seed-strapi-categories.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/react": "^4.4.2",
|
"@astrojs/react": "^4.4.2",
|
||||||
|
|||||||
171
scripts/seed-strapi-categories.mjs
Normal file
171
scripts/seed-strapi-categories.mjs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const root = path.resolve(__dirname, "..");
|
||||||
|
const categoriesDir = path.join(root, "src", "content", "categories");
|
||||||
|
|
||||||
|
const defaultEndpoint = "http://localhost:1337/graphql";
|
||||||
|
const endpoint =
|
||||||
|
process.env.STRAPI_GRAPHQL_URL ??
|
||||||
|
process.env.PUBLIC_GRAPHQL_URL ??
|
||||||
|
defaultEndpoint;
|
||||||
|
|
||||||
|
const token =
|
||||||
|
process.env.STRAPI_TOKEN ??
|
||||||
|
process.env.STRAPI_API_TOKEN ??
|
||||||
|
process.env.AUTH_TOKEN;
|
||||||
|
|
||||||
|
function readCategorySource(slug) {
|
||||||
|
const filePath = path.join(categoriesDir, `${slug}.json`);
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
throw new Error(`Missing source file: ${path.relative(root, filePath)}`);
|
||||||
|
}
|
||||||
|
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function discoverCategorySlugs() {
|
||||||
|
if (!fs.existsSync(categoriesDir)) {
|
||||||
|
throw new Error(`Categories directory missing: ${path.relative(root, categoriesDir)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const slugs = [];
|
||||||
|
for (const dirent of fs.readdirSync(categoriesDir, { withFileTypes: true })) {
|
||||||
|
if (!dirent.isFile() || !dirent.name.endsWith(".json")) continue;
|
||||||
|
const slug = path.basename(dirent.name, ".json");
|
||||||
|
const source = readCategorySource(slug);
|
||||||
|
const { group } = source;
|
||||||
|
|
||||||
|
if (group !== "solution" && group !== "service") {
|
||||||
|
throw new Error(
|
||||||
|
`Category ${slug} has invalid group "${String(group)}". Expected "solution" or "service".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
slugs.push(slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slugs.length === 0) {
|
||||||
|
throw new Error(`No *.json categories in ${path.relative(root, categoriesDir)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return slugs;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function gql(query, variables = {}) {
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/graphql-response+json, application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify({ query, variables }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GraphQL HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = await response.json();
|
||||||
|
if (payload.errors?.length) {
|
||||||
|
const messages = payload.errors.map((err) => err.message).join("; ");
|
||||||
|
throw new Error(`GraphQL error: ${messages}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function categoryExists(slug) {
|
||||||
|
const query = `
|
||||||
|
query CategoryBySlug($slug: String!) {
|
||||||
|
categories(filters: { slug: { eq: $slug } }, pagination: { limit: 1 }) {
|
||||||
|
documentId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await gql(query, { slug });
|
||||||
|
return Array.isArray(data?.categories) && data.categories.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createCategory(input) {
|
||||||
|
const mutation = `
|
||||||
|
mutation CreateCategory($data: CategoryInput!) {
|
||||||
|
createCategory(data: $data) {
|
||||||
|
documentId
|
||||||
|
slug
|
||||||
|
title
|
||||||
|
group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await gql(mutation, { data: input });
|
||||||
|
return data?.createCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedCategories(slugs) {
|
||||||
|
let createdCount = 0;
|
||||||
|
let skippedCount = 0;
|
||||||
|
|
||||||
|
for (const slug of slugs) {
|
||||||
|
const source = readCategorySource(slug);
|
||||||
|
const { title, description = "", group } = source;
|
||||||
|
|
||||||
|
if (group !== "solution" && group !== "service") {
|
||||||
|
throw new Error(
|
||||||
|
`Category ${slug} has invalid group "${String(group)}". Expected "solution" or "service".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await categoryExists(slug)) {
|
||||||
|
skippedCount += 1;
|
||||||
|
console.log(`SKIP ${slug} (already exists)`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const created = await createCategory({
|
||||||
|
slug,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
group,
|
||||||
|
});
|
||||||
|
|
||||||
|
createdCount += 1;
|
||||||
|
console.log(
|
||||||
|
`CREATE ${created.slug} -> ${created.title} [group=${created.group}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { createdCount, skippedCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log(`Seeding categories to ${endpoint}`);
|
||||||
|
if (!token) {
|
||||||
|
console.warn(
|
||||||
|
"No STRAPI token provided. If endpoint is protected, set STRAPI_TOKEN."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const slugs = discoverCategorySlugs();
|
||||||
|
const { createdCount, skippedCount } = await seedCategories(slugs);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Done. created=${createdCount}, skipped=${skippedCount}, total=${createdCount + skippedCount}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Решения на базе ИИ",
|
"title": "Решения на базе ИИ",
|
||||||
|
"group": "solution",
|
||||||
"description": "Передовые технологии искусственного интеллекта и машинного обучения для автоматизации бизнес-процессов",
|
"description": "Передовые технологии искусственного интеллекта и машинного обучения для автоматизации бизнес-процессов",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Решения для бизнеса",
|
"title": "Решения для бизнеса",
|
||||||
|
"group": "solution",
|
||||||
"description": "Комплексные IT-решения для автоматизации бизнес-процессов, CRM, ERP и управления предприятием",
|
"description": "Комплексные IT-решения для автоматизации бизнес-процессов, CRM, ERP и управления предприятием",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "САПР и ГИС",
|
"title": "САПР и ГИС",
|
||||||
|
"group": "solution",
|
||||||
"description": "Системы автоматизированного проектирования и геоинформационные системы для инженеров, архитекторов и проектировщиков",
|
"description": "Системы автоматизированного проектирования и геоинформационные системы для инженеров, архитекторов и проектировщиков",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Облачные решения",
|
"title": "Облачные решения",
|
||||||
|
"group": "solution",
|
||||||
"description": "Российские облачные платформы для размещения инфраструктуры, приложений и данных с соблюдением требований законодательства РФ",
|
"description": "Российские облачные платформы для размещения инфраструктуры, приложений и данных с соблюдением требований законодательства РФ",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Консалтинг и проектирование",
|
"title": "Консалтинг и проектирование",
|
||||||
|
"group": "service",
|
||||||
"description": "Экспертный консалтинг по построению и трансформации IT-инфраструктуры. Помогаем разработать стратегию цифровизации, выбрать оптимальные технологии и спланировать миграцию на российские решения.",
|
"description": "Экспертный консалтинг по построению и трансформации IT-инфраструктуры. Помогаем разработать стратегию цифровизации, выбрать оптимальные технологии и спланировать миграцию на российские решения.",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Кибербезопасность",
|
"title": "Кибербезопасность",
|
||||||
|
"group": "solution",
|
||||||
"description": "Комплексные решения для защиты корпоративной инфраструктуры, данных и приложений от современных киберугроз",
|
"description": "Комплексные решения для защиты корпоративной инфраструктуры, данных и приложений от современных киберугроз",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Для госсектора",
|
"title": "Для госсектора",
|
||||||
|
"group": "solution",
|
||||||
"description": "Специализированные решения для государственных органов с соблюдением требований ФСТЭК и ФСБ",
|
"description": "Специализированные решения для государственных органов с соблюдением требований ФСТЭК и ФСБ",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Аппаратные решения",
|
"title": "Аппаратные решения",
|
||||||
|
"group": "solution",
|
||||||
"description": "Комплексные аппаратные решения для построения IT-инфраструктуры: СХД, сетевое оборудование, компоненты",
|
"description": "Комплексные аппаратные решения для построения IT-инфраструктуры: СХД, сетевое оборудование, компоненты",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Поставка и внедрение решений",
|
"title": "Поставка и внедрение решений",
|
||||||
|
"group": "service",
|
||||||
"description": "Полный цикл поставки и внедрения IT-решений: от подбора оборудования и ПО до установки, настройки и ввода в эксплуатацию. Работаем только с сертифицированными российскими решениями.",
|
"description": "Полный цикл поставки и внедрения IT-решений: от подбора оборудования и ПО до установки, настройки и ввода в эксплуатацию. Работаем только с сертифицированными российскими решениями.",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Индустриальные решения",
|
"title": "Индустриальные решения",
|
||||||
|
"group": "solution",
|
||||||
"description": "Системы промышленной автоматизации, SCADA, MES и решения для управления технологическими процессами",
|
"description": "Системы промышленной автоматизации, SCADA, MES и решения для управления технологическими процессами",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Интеграция и разработка",
|
"title": "Интеграция и разработка",
|
||||||
|
"group": "service",
|
||||||
"description": "Разрабатываем и интегрируем ПО любой сложности. От интеграции корпоративных систем до создания уникальных решений с нуля на современных технологических стеках.",
|
"description": "Разрабатываем и интегрируем ПО любой сложности. От интеграции корпоративных систем до создания уникальных решений с нуля на современных технологических стеках.",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Управляемые сервисы",
|
"title": "Управляемые сервисы",
|
||||||
|
"group": "service",
|
||||||
"description": "Передайте управление IT-инфраструктурой профессионалам. Обеспечиваем круглосуточный мониторинг, поддержку и развитие ваших IT-систем с гарантией SLA.",
|
"description": "Передайте управление IT-инфраструктурой профессионалам. Обеспечиваем круглосуточный мониторинг, поддержку и развитие ваших IT-систем с гарантией SLA.",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Российские ноутбуки, ПК и серверы",
|
"title": "Российские ноутбуки, ПК и серверы",
|
||||||
|
"group": "solution",
|
||||||
"description": "Отечественное компьютерное и серверное оборудование для корпоративного сектора и государственных организаций",
|
"description": "Отечественное компьютерное и серверное оборудование для корпоративного сектора и государственных организаций",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Российское ПО",
|
"title": "Российское ПО",
|
||||||
|
"group": "solution",
|
||||||
"description": "Комплексные программные решения от российских разработчиков для бизнеса, образования и государственного сектора",
|
"description": "Комплексные программные решения от российских разработчиков для бизнеса, образования и государственного сектора",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Специализированные услуги по отраслям",
|
"title": "Специализированные услуги по отраслям",
|
||||||
|
"group": "service",
|
||||||
"description": "Отраслевая экспертиза в финансах, промышленности, здравоохранении, образовании, ритейле и телекоме. Понимаем специфику бизнеса и требования регуляторов.",
|
"description": "Отраслевая экспертиза в финансах, промышленности, здравоохранении, образовании, ритейле и телекоме. Понимаем специфику бизнеса и требования регуляторов.",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Услуги по подписке",
|
"title": "Услуги по подписке",
|
||||||
|
"group": "service",
|
||||||
"description": "Современная модель потребления IT-услуг. Платите только за использованные ресурсы, получайте актуальные обновления и профессиональную поддержку без капитальных затрат.",
|
"description": "Современная модель потребления IT-услуг. Платите только за использованные ресурсы, получайте актуальные обновления и профессиональную поддержку без капитальных затрат.",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Обслуживание и сопровождение",
|
"title": "Обслуживание и сопровождение",
|
||||||
|
"group": "service",
|
||||||
"description": "Надежная техническая поддержка и обслуживание IT-инфраструктуры. Гарантируем стабильную работу всех систем с минимальным временем простоя.",
|
"description": "Надежная техническая поддержка и обслуживание IT-инфраструктуры. Гарантируем стабильную работу всех систем с минимальным временем простоя.",
|
||||||
"vendors": [
|
"vendors": [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user