/opt/canhelp/apps/api/src/routes
NameSizeModeActions
admin.ts950570644editdlrm
auto-match.ts74060644editdlrm
auto-response.ts81110644editdlrm
availability.ts72940644editdlrm
categories.ts18670644editdlrm
chat.ts93770644editdlrm
dashboard.ts51420644editdlrm
favorites.ts44410644editdlrm
fomo.ts68440644editdlrm
google.ts48720644editdlrm
health.ts12140644editdlrm
i18n.ts1670860644editdlrm
locations.ts17810644editdlrm
notifications.ts28620644editdlrm
offers.ts100470644editdlrm
payments.ts211670644editdlrm
plans.ts13520644editdlrm
portfolio.ts66580644editdlrm
price-list.ts144170644editdlrm
reports.ts20070644editdlrm
reviews.ts27830644editdlrm
skill-suggestions.ts10350644editdlrm
specialist-cards.ts186410644editdlrm
stats.ts10930644editdlrm
statuses.ts37310644editdlrm
support.ts84520644editdlrm
tasks.ts300180644editdlrm
uploads.ts15260644editdlrm
users.ts371570644editdlrm
Edit: /opt/canhelp/apps/api/src/routes/categories.ts (1867B)
import { Hono } from 'hono' import { eq, isNull, asc } from 'drizzle-orm' import { db } from '../db.js' import { categories } from '@canhelp/db' import { redis } from '../redis.js' const CACHE_KEY = 'categories:tree:v2' const CACHE_TTL = 600 // 10 min const app = new Hono() function rowToCategory(row: typeof categories.$inferSelect) { return { id: row.id, slug: row.slug, icon: row.icon, names: { el: row.namesEl, en: row.namesEn, ru: row.namesRu, uk: row.namesUk ?? row.namesRu }, parentId: row.parentId, order: row.order, isActive: row.isActive, } } // Build tree from flat list function buildTree(rows: typeof categories.$inferSelect[]) { const map: Record = {} const roots: any[] = [] for (const row of rows) { map[row.id] = { ...rowToCategory(row), children: [] } } for (const row of rows) { if (row.parentId && map[row.parentId]) { map[row.parentId].children.push(map[row.id]) } else { roots.push(map[row.id]) } } return roots } // GET /categories — returns tree of active categories app.get('/', async (c) => { const cached = await redis.get(CACHE_KEY).catch(() => null) if (cached) return c.json(JSON.parse(cached)) const rows = await db .select() .from(categories) .where(eq(categories.isActive, true)) .orderBy(asc(categories.order), asc(categories.namesEl)) const tree = buildTree(rows) await redis.set(CACHE_KEY, JSON.stringify(tree), 'EX', CACHE_TTL).catch(() => {}) return c.json(tree) }) // GET /categories/flat — flat list for dropdowns (includes inactive for admin) app.get('/flat', async (c) => { const rows = await db .select() .from(categories) .orderBy(asc(categories.order), asc(categories.namesEl)) return c.json(rows.map(rowToCategory)) }) export { buildTree, rowToCategory } export default app