/opt/canhelp/apps/api/src
Edit: /opt/canhelp/apps/api/src/sync-categories-from-prod.ts (2792B)
import 'dotenv/config'
import { categories } from '@canhelp/db'
import { db } from './db.js'
import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
const PROD_CATEGORIES_URL = process.env.PROD_CATEGORIES_URL || 'https://api.canhelp.gr/api/categories'
type RemoteCategory = {
slug: string
icon?: string | null
names?: {
el?: string | null
en?: string | null
ru?: string | null
uk?: string | null
} | null
order?: number | null
isActive?: boolean | null
children?: RemoteCategory[] | null
}
function normalizeName(value: string | null | undefined, fallback: string): string {
const v = (value ?? '').trim()
return v || fallback
}
async function fetchProductionCategories(): Promise
{
const res = await fetch(PROD_CATEGORIES_URL)
if (!res.ok) {
throw new Error(`Failed to fetch ${PROD_CATEGORIES_URL}: ${res.status} ${res.statusText}`)
}
const data = await res.json()
if (!Array.isArray(data)) {
throw new Error('Unexpected categories payload: expected array')
}
return data as RemoteCategory[]
}
async function insertTree(
tx: NodePgDatabase,
nodes: RemoteCategory[],
parentId: string | null,
): Promise {
let inserted = 0
for (let index = 0; index < nodes.length; index += 1) {
const node = nodes[index]
if (!node?.slug || typeof node.slug !== 'string') continue
const slug = node.slug.trim()
if (!slug) continue
const names = node.names ?? {}
const fallback = slug.replace(/-/g, ' ')
const [created] = await tx
.insert(categories)
.values({
slug,
icon: (node.icon ?? '').trim() || '📦',
namesEl: normalizeName(names.el, fallback),
namesEn: normalizeName(names.en, fallback),
namesRu: normalizeName(names.ru, fallback),
namesUk: (names.uk ?? '').trim() || null,
parentId,
order: typeof node.order === 'number' ? node.order : index + 1,
isActive: typeof node.isActive === 'boolean' ? node.isActive : true,
})
.returning({ id: categories.id })
inserted += 1
const children = Array.isArray(node.children) ? node.children : []
if (children.length > 0) {
inserted += await insertTree(tx, children, created.id)
}
}
return inserted
}
async function main() {
console.log(`Syncing categories from: ${PROD_CATEGORIES_URL}`)
const tree = await fetchProductionCategories()
const total = await db.transaction(async (tx) => {
await tx.delete(categories)
return insertTree(tx as unknown as NodePgDatabase, tree, null)
})
console.log(`Done. Imported categories: ${total}`)
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error('Category sync failed:', error)
process.exit(1)
})