Jake Shore 96e52666c5 MCPEngine full sync — studio scaffold, factory v2, server updates, state.json — 2026-02-12
=== NEW ===
- studio/ — MCPEngine Studio scaffold (Next.js monorepo, build plan)
- docs/FACTORY-V2.md — Factory v2 architecture doc
- docs/CALENDLY_MCP_BUILD_SUMMARY.md — Calendly MCP build report

=== UPDATED SERVERS ===
- fieldedge: Added jobs-tools, UI build script, main entry update
- lightspeed: Updated main + server entry points
- squarespace: Added collection-browser + page-manager apps
- toast: Added main + server entry points

=== INFRA ===
- infra/command-center/state.json — Updated pipeline state
- infra/command-center/FACTORY-V2.md — Factory v2 operator playbook
2026-02-12 17:58:33 -05:00

92 lines
2.7 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { db, marketplaceListings } from '@mcpengine/db';
import { eq, ilike, and, desc, count, or, sql } from 'drizzle-orm';
// ── GET /api/marketplace — public listing search ────────────────────────────
export async function GET(req: NextRequest) {
try {
const url = new URL(req.url);
const query = url.searchParams.get('q') || '';
const category = url.searchParams.get('category') || '';
const official = url.searchParams.get('official');
const featured = url.searchParams.get('featured');
const page = Math.max(1, parseInt(url.searchParams.get('page') || '1', 10));
const limit = Math.min(50, Math.max(1, parseInt(url.searchParams.get('limit') || '24', 10)));
const offset = (page - 1) * limit;
// Build filter conditions — only published listings
const conditions = [eq(marketplaceListings.status, 'published')];
if (category) {
conditions.push(eq(marketplaceListings.category, category));
}
if (official === 'true') {
conditions.push(eq(marketplaceListings.isOfficial, true));
}
if (featured === 'true') {
conditions.push(eq(marketplaceListings.isFeatured, true));
}
if (query) {
conditions.push(
or(
ilike(marketplaceListings.name, `%${query}%`),
ilike(marketplaceListings.description, `%${query}%`),
ilike(marketplaceListings.slug, `%${query}%`),
)!,
);
}
const where = and(...conditions);
const [rows, totalResult] = await Promise.all([
db
.select()
.from(marketplaceListings)
.where(where)
.orderBy(
desc(marketplaceListings.isFeatured),
desc(marketplaceListings.forkCount),
desc(marketplaceListings.createdAt),
)
.limit(limit)
.offset(offset),
db
.select({ count: count() })
.from(marketplaceListings)
.where(where),
]);
const total = totalResult[0]?.count ?? 0;
// Collect distinct categories for faceting
const categories = await db
.selectDistinct({ category: marketplaceListings.category })
.from(marketplaceListings)
.where(eq(marketplaceListings.status, 'published'));
return NextResponse.json({
data: rows,
meta: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
categories: categories
.map((c) => c.category)
.filter(Boolean)
.sort(),
},
});
} catch (error) {
console.error('[GET /api/marketplace]', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 },
);
}
}