feat: Go backend, enhanced search, new widgets, Docker deploy
Major changes: - Add Go backend (backend/) with microservices architecture - Enhanced master-agents-svc: reranker, content-classifier, stealth-crawler, proxy-manager, media-search, fastClassifier, language detection - New web-svc widgets: KnowledgeCard, ProductCard, ProfileCard, VideoCard, UnifiedCard, CardGallery, InlineImageGallery, SourcesPanel, RelatedQuestions - Improved discover-svc with discover-db integration - Docker deployment improvements (Caddyfile, vendor.sh, BUILD.md) - Library-svc: project_id schema migration - Remove deprecated finance-svc and travel-svc - Localization improvements across services Made-with: Cursor
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM node:22-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY --from=npm-cache / /tmp/npm-cache
|
||||
RUN npm install --cache /tmp/npm-cache --prefer-offline --no-audit
|
||||
COPY tsconfig.json ./
|
||||
COPY src ./src
|
||||
RUN npm run build
|
||||
@@ -9,7 +11,8 @@ RUN npm run build
|
||||
FROM node:22-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --omit=dev
|
||||
COPY --from=npm-cache / /tmp/npm-cache
|
||||
RUN npm install --omit=dev --cache /tmp/npm-cache --prefer-offline --no-audit
|
||||
COPY --from=builder /app/dist ./dist
|
||||
EXPOSE 3006
|
||||
CMD ["node", "dist/index.js"]
|
||||
|
||||
@@ -33,11 +33,28 @@ const STUB_COLLECTIONS = [
|
||||
{ id: 'sp-transcripts', title: 'S&P 500 Transcripts', category: 'finance', description: 'Earnings call transcripts' },
|
||||
];
|
||||
|
||||
/** Popular Space templates — Perplexity-style coverage */
|
||||
const STUB_TEMPLATES = [
|
||||
{ id: 'research-assistant', title: 'Research Assistant', category: 'research', description: 'Deep research, synthesis, sources' },
|
||||
{ id: 'writing-editing', title: 'Writing & Editing', category: 'writing', description: 'Drafts, proofreading, style' },
|
||||
{ id: 'code-assistant', title: 'Code Assistant', category: 'coding', description: 'Debug, explain, refactor code' },
|
||||
{ id: 'finance-research', title: 'Finance Research', category: 'finance', description: 'Market analysis, earnings, SEC filings' },
|
||||
{ id: 'product-docs', title: 'Product Documentation', category: 'product', description: 'Technical docs, specs, roadmaps' },
|
||||
{ id: 'marketing-campaigns', title: 'Marketing Campaigns', category: 'marketing', description: 'Campaign briefs, analytics, copy' },
|
||||
{ id: 'travel-planning', title: 'Travel Planning', category: 'travel', description: 'Itineraries, hotels, destinations' },
|
||||
{ id: 'academic-research', title: 'Academic Research', category: 'academic', description: 'Papers, citations, literature review' },
|
||||
{ id: 'legal-research', title: 'Legal Research', category: 'legal', description: 'Case law, contracts, compliance' },
|
||||
{ id: 'healthcare-assistant', title: 'Healthcare Assistant', category: 'healthcare', description: 'Medical info, summaries, protocols' },
|
||||
{ id: 'hr-recruiting', title: 'HR & Recruiting', category: 'hr', description: 'Job descriptions, screening, onboarding' },
|
||||
{ id: 'sales-assistant', title: 'Sales Assistant', category: 'sales', description: 'Pitch decks, outreach, CRM' },
|
||||
{ id: 'data-analysis', title: 'Data Analysis', category: 'data', description: 'Stats, insights, visualizations' },
|
||||
{ id: 'content-creation', title: 'Content Creation', category: 'content', description: 'Blog posts, social, SEO' },
|
||||
{ id: 'job-search', title: 'Job Search', category: 'career', description: 'Resume, cover letters, interviews' },
|
||||
{ id: 'competitive-intel', title: 'Competitive Intelligence', category: 'research', description: 'Competitors, market landscape' },
|
||||
{ id: 'due-diligence', title: 'Due Diligence', category: 'finance', description: 'M&A, investments, valuations' },
|
||||
{ id: 'grant-writing', title: 'Grant Writing', category: 'writing', description: 'Proposals, budgets, narratives' },
|
||||
{ id: 'customer-support', title: 'Customer Support', category: 'support', description: 'Tickets, FAQs, escalation' },
|
||||
{ id: 'project-management', title: 'Project Management', category: 'product', description: 'Roadmaps, timelines, standups' },
|
||||
];
|
||||
|
||||
const app = Fastify({ logger: true });
|
||||
@@ -69,19 +86,89 @@ app.get<{ Querystring: { category?: string } }>('/api/v1/templates', async (req,
|
||||
return { items };
|
||||
});
|
||||
|
||||
/** In-memory projects storage (userId -> Map<projectId, project>) */
|
||||
const projectsStore = new Map<
|
||||
string,
|
||||
Map<
|
||||
string,
|
||||
{ id: string; userId: string; title: string; description: string; links: string[]; instructions: string; createdAt: string }
|
||||
>
|
||||
>();
|
||||
|
||||
function getOrCreateUserProjects(userId: string) {
|
||||
if (!projectsStore.has(userId)) {
|
||||
projectsStore.set(userId, new Map());
|
||||
}
|
||||
return projectsStore.get(userId)!;
|
||||
}
|
||||
|
||||
app.get('/api/v1/projects', async (req, reply) => {
|
||||
const userId = await getUserIdFromToken(req.headers.authorization);
|
||||
if (!userId) return reply.status(401).send({ error: 'Authorization required' });
|
||||
return reply.send({ items: [] });
|
||||
const userProjects = getOrCreateUserProjects(userId);
|
||||
const items = Array.from(userProjects.values()).sort(
|
||||
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
);
|
||||
return reply.send({ items });
|
||||
});
|
||||
|
||||
app.post('/api/v1/projects', async (req, reply) => {
|
||||
app.get<{ Params: { id: string } }>('/api/v1/projects/:id', async (req, reply) => {
|
||||
const userId = await getUserIdFromToken(req.headers.authorization);
|
||||
if (!userId) return reply.status(401).send({ error: 'Authorization required' });
|
||||
return reply.status(501).send({
|
||||
error: 'Not implemented',
|
||||
message: 'Projects CRUD will be available in a future release',
|
||||
});
|
||||
const userProjects = getOrCreateUserProjects(userId);
|
||||
const project = userProjects.get(req.params.id);
|
||||
if (!project) return reply.status(404).send({ error: 'Project not found' });
|
||||
const p = project as { instructions?: string };
|
||||
if (p.instructions === undefined) p.instructions = '';
|
||||
return reply.send(project);
|
||||
});
|
||||
|
||||
app.post<{
|
||||
Body: { id?: string; title: string; description?: string };
|
||||
}>('/api/v1/projects', async (req, reply) => {
|
||||
const userId = await getUserIdFromToken(req.headers.authorization);
|
||||
if (!userId) return reply.status(401).send({ error: 'Authorization required' });
|
||||
const { id, title, description = '' } = req.body ?? {};
|
||||
const projectId = id ?? crypto.randomUUID();
|
||||
const now = new Date().toISOString();
|
||||
const project = {
|
||||
id: projectId,
|
||||
userId,
|
||||
title: (title ?? 'Новый проект').trim(),
|
||||
description: (description ?? '').trim(),
|
||||
links: [] as string[],
|
||||
instructions: '',
|
||||
createdAt: now,
|
||||
};
|
||||
getOrCreateUserProjects(userId).set(projectId, project);
|
||||
return reply.send(project);
|
||||
});
|
||||
|
||||
app.patch<{
|
||||
Params: { id: string };
|
||||
Body: { title?: string; description?: string; links?: string[]; instructions?: string };
|
||||
}>('/api/v1/projects/:id', async (req, reply) => {
|
||||
const userId = await getUserIdFromToken(req.headers.authorization);
|
||||
if (!userId) return reply.status(401).send({ error: 'Authorization required' });
|
||||
const userProjects = getOrCreateUserProjects(userId);
|
||||
const project = userProjects.get(req.params.id);
|
||||
if (!project) return reply.status(404).send({ error: 'Project not found' });
|
||||
if (!('instructions' in project)) (project as { instructions?: string }).instructions = '';
|
||||
const { title, description, links, instructions } = req.body ?? {};
|
||||
if (title !== undefined) project.title = title.trim();
|
||||
if (description !== undefined) project.description = description.trim();
|
||||
if (links !== undefined) project.links = Array.isArray(links) ? links : project.links;
|
||||
if (instructions !== undefined) project.instructions = typeof instructions === 'string' ? instructions : project.instructions;
|
||||
return reply.send(project);
|
||||
});
|
||||
|
||||
app.delete<{ Params: { id: string } }>('/api/v1/projects/:id', async (req, reply) => {
|
||||
const userId = await getUserIdFromToken(req.headers.authorization);
|
||||
if (!userId) return reply.status(401).send({ error: 'Authorization required' });
|
||||
const userProjects = getOrCreateUserProjects(userId);
|
||||
if (!userProjects.has(req.params.id)) return reply.status(404).send({ error: 'Project not found' });
|
||||
userProjects.delete(req.params.id);
|
||||
return reply.send({ ok: true });
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user