refactor: consolidate codebase — remove archived code, deduplicate packages, standardize middleware

- Delete 17 server-archived/ directories (consolidated into apps/api/)
- Delete apps-archived/ (clock, wisekeep) and services-archived/ (it-landing, ollama-metrics-proxy)
- Fix type safety: replace all `any` casts in cross-app-queries.ts with proper Local* types
- Remove duplicate shared-auth-stores package (identical copy of shared-auth-ui/stores/)
- Remove duplicate theme store from shared-stores (canonical version in shared-theme)
- Migrate memoro-server rate-limiter to shared-hono/rateLimitMiddleware
- Migrate uload-server JWT auth + error handler to shared-hono (authMiddleware, errorHandler)
- Migrate arcade-server error handling to shared-hono
- Merge shared-profile-ui and shared-app-onboarding into shared-ui
- Unify /clock route into /times/clock, remove redirect stubs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-03 12:55:58 +02:00
parent 7ee57b7afd
commit d8ce4eaf34
309 changed files with 172 additions and 21667 deletions

View file

@ -1,20 +0,0 @@
{
"name": "@guides/server",
"version": "0.1.0",
"private": true,
"description": "Guides server (Hono + Bun) — web import, guide sharing, AI generation",
"type": "module",
"scripts": {
"dev": "bun run --watch src/index.ts",
"start": "bun run src/index.ts",
"type-check": "bun x tsc --noEmit"
},
"dependencies": {
"@manacore/shared-hono": "workspace:*",
"hono": "^4.7.0"
},
"devDependencies": {
"@types/bun": "^1.2.0",
"typescript": "^5.9.3"
}
}

View file

@ -1,49 +0,0 @@
/**
* Guides Server Hono + Bun
*
* Compute-only server for features that need server-side logic:
* - Web import: URL structured guide via mana-search
* - AI generation: text/paste guide via mana-llm
* - Guide sharing: public guide links
*
* All CRUD is handled client-side via local-first + mana-sync.
*/
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { importRoutes } from './routes/import.js';
import { shareRoutes } from './routes/share.js';
const app = new Hono();
// Middleware
app.use('*', logger());
app.use(
'*',
cors({
origin: (process.env.CORS_ORIGINS ?? 'http://localhost:5200').split(','),
allowMethods: ['GET', 'POST', 'OPTIONS'],
allowHeaders: ['Authorization', 'Content-Type'],
credentials: true,
})
);
// Routes
app.route('/api/v1/import', importRoutes);
app.route('/api/v1/share', shareRoutes);
// Health check
app.get('/health', (c) =>
c.json({
status: 'ok',
service: 'guides-server',
runtime: 'bun',
timestamp: new Date().toISOString(),
})
);
const port = Number(process.env.PORT ?? 3027);
console.log(`🚀 Guides server (Hono + Bun) starting on port ${port}`);
export default { port, fetch: app.fetch };

View file

@ -1,171 +0,0 @@
/**
* Import routes convert URLs or raw text into structured guide data.
*
* POST /api/v1/import/url fetch URL via mana-search extract, return guide draft
* POST /api/v1/import/text parse plain text / markdown into guide steps
* POST /api/v1/import/ai send to mana-llm to generate structured guide
*/
import { Hono } from 'hono';
export const importRoutes = new Hono();
const MANA_SEARCH_URL = process.env.MANA_SEARCH_URL ?? 'http://localhost:3021';
const MANA_LLM_URL = process.env.MANA_LLM_URL ?? 'http://localhost:3030';
// ─── URL Import ─────────────────────────────────────────────────────────────
importRoutes.post('/url', async (c) => {
const body = await c.req.json<{ url: string }>();
const { url } = body;
if (!url || !URL.canParse(url)) {
return c.json({ error: 'Ungültige URL' }, 400);
}
// Extract content via mana-search
let extracted: { title?: string; content?: string; markdown?: string } = {};
try {
const res = await fetch(`${MANA_SEARCH_URL}/api/v1/extract`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url, options: { includeMarkdown: true } }),
});
if (res.ok) {
extracted = await res.json();
}
} catch (e) {
console.error('mana-search extract failed:', e);
}
const content = extracted.markdown ?? extracted.content ?? '';
if (!content) {
return c.json({ error: 'Inhalt konnte nicht extrahiert werden' }, 422);
}
// Use mana-llm to turn raw content into structured guide
return await generateGuideFromText(c, {
title: extracted.title,
text: content,
sourceUrl: url,
});
});
// ─── Text/Markdown Import ────────────────────────────────────────────────────
importRoutes.post('/text', async (c) => {
const body = await c.req.json<{ text: string; title?: string }>();
const { text, title } = body;
if (!text?.trim()) {
return c.json({ error: 'Kein Text angegeben' }, 400);
}
return await generateGuideFromText(c, { text, title });
});
// ─── AI Generation ───────────────────────────────────────────────────────────
importRoutes.post('/ai', async (c) => {
const body = await c.req.json<{ prompt: string; title?: string }>();
const { prompt, title } = body;
if (!prompt?.trim()) {
return c.json({ error: 'Kein Prompt angegeben' }, 400);
}
return await generateGuideFromText(c, {
text: prompt,
title,
isAiPrompt: true,
});
});
// ─── Shared: LLM guide generation ───────────────────────────────────────────
async function generateGuideFromText(
c: Parameters<Parameters<typeof Hono.prototype.post>[1]>[0],
opts: { text: string; title?: string; sourceUrl?: string; isAiPrompt?: boolean }
) {
const systemPrompt = `Du bist ein Experte für das Erstellen strukturierter Schritt-für-Schritt-Anleitungen.
Analysiere den folgenden Text und erstelle daraus eine strukturierte Anleitung im JSON-Format.
Antworte NUR mit einem validen JSON-Objekt in diesem exakten Format:
{
"title": "Titel der Anleitung",
"description": "Kurze Beschreibung (1-2 Sätze)",
"category": "Technik|Kochen|Sport|Lernen|Arbeit|Haushalt|Hobby|Allgemein",
"difficulty": "easy|medium|hard",
"estimatedMinutes": Zahl,
"tags": ["tag1", "tag2"],
"sections": [
{
"title": "Abschnitt-Titel (optional, leer lassen wenn keine Sections nötig)",
"steps": [
{
"title": "Schritt-Titel",
"content": "Optionale Details oder Code",
"type": "instruction|warning|tip|checkpoint|code"
}
]
}
]
}
Regeln:
- Maximal 3-4 Abschnitte, maximal 8-10 Schritte pro Abschnitt
- type "warning" nur bei wirklichen Warnungen/Gefahren
- type "tip" für hilfreiche Hinweise
- type "code" wenn der Inhalt Kommandos oder Code enthält
- type "checkpoint" für Überprüfungsschritte
- Wenn kein sinnvolles Abschnitt-System, eine leere Section mit title ""
- Schritt-Titel: prägnant, maximal 80 Zeichen
- Auf Deutsch antworten`;
const userMessage = opts.isAiPrompt
? `Erstelle eine Anleitung für: ${opts.text}`
: `Hier ist der Inhalt, den du in eine Anleitung umwandeln sollst:\n\n${opts.text.slice(0, 8000)}`;
try {
const llmRes = await fetch(`${MANA_LLM_URL}/api/v1/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userMessage },
],
model: 'claude-haiku-4-5-20251001',
temperature: 0.3,
maxTokens: 4096,
}),
});
if (!llmRes.ok) {
throw new Error(`LLM error: ${llmRes.status}`);
}
const llmData = await llmRes.json<{ content: string }>();
const rawJson = llmData.content.trim();
// Extract JSON from potential markdown code fences
const jsonMatch = rawJson.match(/```(?:json)?\s*([\s\S]*?)\s*```/) ?? [null, rawJson];
const parsed = JSON.parse(jsonMatch[1] ?? rawJson);
return c.json({
guide: {
title: opts.title ?? parsed.title,
description: parsed.description,
category: parsed.category ?? 'Allgemein',
difficulty: parsed.difficulty ?? 'medium',
estimatedMinutes: parsed.estimatedMinutes,
tags: parsed.tags ?? [],
sourceUrl: opts.sourceUrl,
},
sections: parsed.sections ?? [],
});
} catch (e) {
console.error('Guide generation failed:', e);
return c.json({ error: 'Guide-Generierung fehlgeschlagen', details: String(e) }, 500);
}
}

View file

@ -1,50 +0,0 @@
/**
* Share routes public guide links (Phase 3, in-memory store for MVP)
*
* POST /api/v1/share create shareable link for a guide snapshot
* GET /api/v1/share/:token retrieve shared guide by token
*/
import { Hono } from 'hono';
export const shareRoutes = new Hono();
// In-memory store for shared guides (replace with DB in Phase 4)
const sharedGuides = new Map<string, { guide: unknown; sections: unknown[]; createdAt: string; expiresAt: string }>();
shareRoutes.post('/', async (c) => {
const body = await c.req.json<{ guide: unknown; sections: unknown[] }>();
if (!body.guide) {
return c.json({ error: 'Kein Guide-Inhalt angegeben' }, 400);
}
const token = crypto.randomUUID().replace(/-/g, '').slice(0, 12);
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); // 7 days
sharedGuides.set(token, {
guide: body.guide,
sections: body.sections ?? [],
createdAt: new Date().toISOString(),
expiresAt,
});
const baseUrl = process.env.PUBLIC_BASE_URL ?? 'http://localhost:5200';
return c.json({ token, url: `${baseUrl}/shared/${token}`, expiresAt });
});
shareRoutes.get('/:token', (c) => {
const { token } = c.req.param();
const shared = sharedGuides.get(token);
if (!shared) {
return c.json({ error: 'Guide nicht gefunden oder Link abgelaufen' }, 404);
}
if (new Date(shared.expiresAt) < new Date()) {
sharedGuides.delete(token);
return c.json({ error: 'Dieser Link ist abgelaufen' }, 410);
}
return c.json(shared);
});

View file

@ -1,16 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022"],
"types": ["bun-types"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}