initial 2
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-06-26 09:26:50 +01:00
parent 194330fb47
commit 3b37368e7d
213 changed files with 14688 additions and 1 deletions

View File

@@ -0,0 +1,23 @@
import { handleApi } from "@/lib/api";
import {
getAchievements,
getAllAchievementDefinitions,
checkAchievements,
} from "@/lib/services/achievements";
import { requireUser } from "@/lib/services/user";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
const unlocked = await getAchievements(user.id);
const all = getAllAchievementDefinitions();
return { unlocked, all };
});
}
export async function POST() {
return handleApi(async () => {
const { user } = await requireUser();
return checkAchievements(user.id);
});
}

View File

@@ -0,0 +1,16 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { undoAction } from "@/lib/services/undo";
export async function POST(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
return handleApi(async () => {
const { user } = await requireUser();
const result = await undoAction(user.id, id);
if (!result.ok) throw new Error(result.error ?? "Undo failed");
return result;
});
}

View File

@@ -0,0 +1,11 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { getRecentActions } from "@/lib/services/action-events";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
const actions = await getRecentActions(user.id, 30);
return { actions };
});
}

View File

@@ -0,0 +1,15 @@
import { handleApi } from "@/lib/api";
import { applyTemplateToDate } from "@/lib/services/adventure";
import { requireUser } from "@/lib/services/user";
export async function POST(
request: Request,
{ params }: { params: Promise<{ date: string }> }
) {
const { date } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return applyTemplateToDate(user.id, date, body.templateId, body.force === true);
});
}

View File

@@ -0,0 +1,29 @@
import { handleApi } from "@/lib/api";
import { updateAdventureItem, updateDailyItemMeta, deleteCustomDailyItem } from "@/lib/services/adventure";
import { requireUser } from "@/lib/services/user";
export async function PATCH(
request: Request,
{ params }: { params: Promise<{ date: string; id: string }> }
) {
const { date, id } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
if (body.label !== undefined || body.enabled !== undefined || body.config !== undefined) {
return updateDailyItemMeta(user.id, date, id, body);
}
return updateAdventureItem(user.id, date, id, body);
});
}
export async function DELETE(
_request: Request,
{ params }: { params: Promise<{ date: string; id: string }> }
) {
const { date, id } = await params;
return handleApi(async () => {
const { user } = await requireUser();
return deleteCustomDailyItem(user.id, date, id);
});
}

View File

@@ -0,0 +1,15 @@
import { handleApi } from "@/lib/api";
import { addCustomDailyItem } from "@/lib/services/adventure";
import { requireUser } from "@/lib/services/user";
export async function POST(
request: Request,
{ params }: { params: Promise<{ date: string }> }
) {
const { date } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return addCustomDailyItem(user.id, date, body);
});
}

View File

@@ -0,0 +1,15 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { quickLog } from "@/lib/services/quick-log";
export async function POST(
request: Request,
{ params }: { params: Promise<{ date: string }> }
) {
const { date } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return quickLog(user.id, date, body);
});
}

View File

@@ -0,0 +1,15 @@
import { handleApi } from "@/lib/api";
import { setRestDay } from "@/lib/services/adventure";
import { requireUser } from "@/lib/services/user";
export async function POST(
_request: Request,
{ params }: { params: Promise<{ date: string }> }
) {
const { date } = await params;
return handleApi(async () => {
const { user } = await requireUser();
const result = await setRestDay(user.id, date);
return { ok: true, actionEventId: result.actionEventId };
});
}

View File

@@ -0,0 +1,32 @@
import { handleApi } from "@/lib/api";
import { getDailyAdventure, updateDaySettings } from "@/lib/services/adventure";
import { requireUser } from "@/lib/services/user";
export async function GET(
_request: Request,
{ params }: { params: Promise<{ date: string }> }
) {
const { date } = await params;
return handleApi(async () => {
const { user } = await requireUser();
return getDailyAdventure(user.id, date);
});
}
export async function PATCH(
request: Request,
{ params }: { params: Promise<{ date: string }> }
) {
const { date } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
if (body.workHoursTarget !== undefined || body.dayMode !== undefined) {
return updateDaySettings(user.id, date, {
workHoursTarget: body.workHoursTarget,
dayMode: body.dayMode,
});
}
throw new Error("No valid updates");
});
}

View File

@@ -0,0 +1,26 @@
import { handleApi } from "@/lib/api";
import { updateDailyTodo, deleteDailyTodo } from "@/lib/services/adventure";
import { requireUser } from "@/lib/services/user";
export async function PATCH(
request: Request,
{ params }: { params: Promise<{ date: string; id: string }> }
) {
const { date, id } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return updateDailyTodo(user.id, date, id, body);
});
}
export async function DELETE(
_request: Request,
{ params }: { params: Promise<{ date: string; id: string }> }
) {
const { date, id } = await params;
return handleApi(async () => {
const { user } = await requireUser();
return deleteDailyTodo(user.id, date, id);
});
}

View File

@@ -0,0 +1,15 @@
import { handleApi } from "@/lib/api";
import { createDailyTodo } from "@/lib/services/adventure";
import { requireUser } from "@/lib/services/user";
export async function POST(
request: Request,
{ params }: { params: Promise<{ date: string }> }
) {
const { date } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return createDailyTodo(user.id, date, body.label);
});
}

View File

@@ -0,0 +1,11 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { detectCatchUpGaps } from "@/lib/services/catch-up";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
const gaps = await detectCatchUpGaps(user.id);
return { gaps };
});
}

View File

@@ -0,0 +1,10 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { getWhatAiKnows } from "@/lib/services/ai-chat";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
return getWhatAiKnows(user.id);
});
}

View File

@@ -0,0 +1,40 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { getChatSession, sendChatMessage, archiveChatSession } from "@/lib/services/ai-chat";
export async function GET(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
return handleApi(async () => {
const { user } = await requireUser();
const data = await getChatSession(user.id, id);
if (!data) throw new Error("Session not found");
return data;
});
}
export async function DELETE(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
return handleApi(async () => {
const { user } = await requireUser();
await archiveChatSession(user.id, id);
return { ok: true };
});
}
export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return sendChatMessage(user.id, id, body.content ?? "");
});
}

View File

@@ -0,0 +1,19 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { listChatSessions, createChatSession } from "@/lib/services/ai-chat";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
const sessions = await listChatSessions(user.id);
return { sessions };
});
}
export async function POST(request: Request) {
const body = await request.json().catch(() => ({}));
return handleApi(async () => {
const { user } = await requireUser();
return createChatSession(user.id, body.title, body.featureContext);
});
}

View File

@@ -0,0 +1,33 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import {
getAiBehaviorConfig,
getAiProviderConfig,
saveAiBehaviorConfig,
saveAiProviderConfig,
} from "@/lib/services/ai-config";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
const behavior = await getAiBehaviorConfig(user.id);
const provider = await getAiProviderConfig(user.id);
return { behavior, provider };
});
}
export async function PATCH(request: Request) {
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
let behavior = await getAiBehaviorConfig(user.id);
let provider = await getAiProviderConfig(user.id);
if (body.behavior) {
behavior = await saveAiBehaviorConfig(user.id, body.behavior);
}
if (body.provider) {
provider = await saveAiProviderConfig(user.id, body.provider);
}
return { behavior, provider };
});
}

View File

@@ -0,0 +1,19 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { buildMentorContext, formatContextForPrompt } from "@/lib/services/ai-context";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const message = searchParams.get("message") ?? "";
const feature = searchParams.get("feature") ?? "mentor";
return handleApi(async () => {
const { user } = await requireUser();
const ctx = await buildMentorContext(user.id, { userMessage: message, feature, logContext: false });
return {
layers: ctx.layers,
tokenEstimate: ctx.tokenEstimate,
memoryIds: ctx.memoryIds,
formatted: formatContextForPrompt(ctx),
};
});
}

View File

@@ -0,0 +1,21 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { getCachedHealth, runHealthCheck } from "@/lib/services/ai-config";
import { getLastHealthLogs } from "@/lib/services/ai-config";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
const cached = await getCachedHealth(user.id);
const logs = await getLastHealthLogs(user.id, 5);
return { health: cached, logs };
});
}
export async function POST() {
return handleApi(async () => {
const { user } = await requireUser();
const health = await runHealthCheck(user.id);
return { health };
});
}

View File

@@ -0,0 +1,40 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { getMemory, updateMemory, archiveMemory } from "@/lib/services/ai-memory";
export async function GET(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
return handleApi(async () => {
const { user } = await requireUser();
const memory = await getMemory(user.id, id);
if (!memory) throw new Error("Memory not found");
return memory;
});
}
export async function PATCH(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return updateMemory(user.id, id, body);
});
}
export async function DELETE(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
return handleApi(async () => {
const { user } = await requireUser();
await archiveMemory(user.id, id);
return { ok: true };
});
}

View File

@@ -0,0 +1,21 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import {
getMemoryLearningSettings,
saveMemoryLearningSettings,
} from "@/lib/services/ai-memory";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
return getMemoryLearningSettings(user.id);
});
}
export async function PATCH(request: Request) {
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return saveMemoryLearningSettings(user.id, body);
});
}

View File

@@ -0,0 +1,13 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { resetAllMemories } from "@/lib/services/ai-memory";
export async function POST(request: Request) {
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
if (!body.confirm) throw new Error("Confirmation required");
await resetAllMemories(user.id);
return { ok: true };
});
}

View File

@@ -0,0 +1,32 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import {
listMemories,
createMemory,
getProfileSummary,
listSuggestions,
exportMemories,
} from "@/lib/services/ai-memory";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
return handleApi(async () => {
const { user } = await requireUser();
const category = searchParams.get("category") ?? undefined;
const q = searchParams.get("q") ?? undefined;
const enabledParam = searchParams.get("enabled");
const enabled = enabledParam === null ? undefined : enabledParam === "true";
const memories = await listMemories(user.id, { category, q, enabled });
const summary = await getProfileSummary(user.id);
const pending = await listSuggestions(user.id, "pending");
return { memories, summary, pendingCount: pending.length };
});
}
export async function POST(request: Request) {
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return createMemory(user.id, body);
});
}

View File

@@ -0,0 +1,15 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { acceptSuggestion } from "@/lib/services/ai-memory";
export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json().catch(() => ({}));
return handleApi(async () => {
const { user } = await requireUser();
return acceptSuggestion(user.id, id, body);
});
}

View File

@@ -0,0 +1,15 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { ignoreSuggestion } from "@/lib/services/ai-memory";
export async function POST(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
return handleApi(async () => {
const { user } = await requireUser();
await ignoreSuggestion(user.id, id);
return { ok: true };
});
}

View File

@@ -0,0 +1,15 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { rejectSuggestion } from "@/lib/services/ai-memory";
export async function POST(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
return handleApi(async () => {
const { user } = await requireUser();
await rejectSuggestion(user.id, id);
return { ok: true };
});
}

View File

@@ -0,0 +1,13 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { listSuggestions } from "@/lib/services/ai-memory";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const status = searchParams.get("status") ?? "pending";
return handleApi(async () => {
const { user } = await requireUser();
const suggestions = await listSuggestions(user.id, status);
return { suggestions };
});
}

View File

@@ -0,0 +1,54 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { listMemories, saveProfileSummary } from "@/lib/services/ai-memory";
import { getProvider } from "@/lib/ai/provider-registry";
import { getAiBehaviorConfig, getAiProviderConfig, buildSystemPrompt } from "@/lib/services/ai-config";
import { getTemplateBody } from "@/lib/services/ai-templates";
import { renderTemplate } from "@/lib/ai/prompts/render";
export async function POST() {
return handleApi(async () => {
const { user } = await requireUser();
const memories = await listMemories(user.id, { enabled: true });
const verified = memories.filter((m) => m.userVerified);
const bulletList = verified
.map((m) => `- [${m.category}] ${m.title}: ${m.content.slice(0, 120)}`)
.join("\n");
const behavior = await getAiBehaviorConfig(user.id);
const providerConfig = await getAiProviderConfig(user.id);
let summaryText = verified.length === 0
? "No verified memories yet. Add memories manually to build your profile."
: bulletList.slice(0, 1500);
if (behavior.enabled && providerConfig.enabled && verified.length > 0) {
try {
const coreSystem = await getTemplateBody(user.id, "system_core");
const templateBody = await getTemplateBody(user.id, "mentor_summary");
const system = buildSystemPrompt(behavior, coreSystem);
const prompt = renderTemplate(templateBody, {
user_name: user.displayName,
context: bulletList,
});
const provider = getProvider(providerConfig.type);
const res = await provider.generateText(providerConfig, {
model: providerConfig.model,
prompt,
system,
format: "text",
temperature: 0.5,
maxTokens: 400,
timeoutMs: providerConfig.timeoutMs,
});
if (res.text.trim()) summaryText = res.text.trim();
} catch {
/* use bullet list fallback */
}
}
const summary = await saveProfileSummary(user.id, summaryText);
return { ...summary, sourceMemoryCount: verified.length };
});
}

View File

@@ -0,0 +1,34 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import {
getProfileSummary,
saveProfileSummary,
exportMemories,
importMemories,
} from "@/lib/services/ai-memory";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
return getProfileSummary(user.id);
});
}
export async function PATCH(request: Request) {
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return saveProfileSummary(user.id, body.summary ?? "");
});
}
export async function POST(request: Request) {
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
if (body.action === "import") {
return importMemories(user.id, body.data, body.overwrite);
}
return exportMemories(user.id);
});
}

View File

@@ -0,0 +1,23 @@
import { handleApi } from "@/lib/api";
import { listProviderTypes } from "@/lib/ai/provider-registry";
import { requireUser } from "@/lib/services/user";
import { getAiProviderConfig } from "@/lib/services/ai-config";
import { getProvider } from "@/lib/ai/provider-registry";
export async function GET() {
return handleApi(async () => {
const providers = listProviderTypes();
return { providers };
});
}
export async function POST() {
return handleApi(async () => {
const { user } = await requireUser();
const config = await getAiProviderConfig(user.id);
const provider = getProvider(config.type);
if (!provider.listModels) return { models: [config.model] };
const models = await provider.listModels(config);
return { models };
});
}

View File

@@ -0,0 +1,15 @@
import { handleApi } from "@/lib/api";
import { dismissSuggestion } from "@/lib/services/explorations";
import { requireUser } from "@/lib/services/user";
export async function POST(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
return handleApi(async () => {
const { user } = await requireUser();
await dismissSuggestion(id, user.id);
return { ok: true };
});
}

View File

@@ -0,0 +1,26 @@
import { handleApi, jsonError } from "@/lib/api";
import { getSuggestions, dismissSuggestion, generateDailyQuests } from "@/lib/services/explorations";
import { requireUser } from "@/lib/services/user";
import { isOllamaAvailable } from "@/lib/services/ai";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const role = searchParams.get("role") ?? undefined;
return handleApi(async () => {
const { user } = await requireUser();
const suggestions = await getSuggestions(user.id, role);
const ollamaAvailable = await isOllamaAvailable();
return { suggestions, ollamaAvailable };
});
}
export async function POST(request: Request) {
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
if (body.action === "generate_quests") {
return generateDailyQuests(user.id);
}
throw new Error("Unknown action");
});
}

View File

@@ -0,0 +1,64 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import {
getPromptTemplate,
updatePromptTemplate,
resetPromptTemplate,
previewTemplate,
getTemplateVersions,
restoreTemplateVersion,
} from "@/lib/services/ai-templates";
export async function GET(
_request: Request,
{ params }: { params: Promise<{ key: string }> }
) {
const { key } = await params;
return handleApi(async () => {
const { user } = await requireUser();
const template = await getPromptTemplate(user.id, key);
const versions = await getTemplateVersions(user.id, key);
return { template, versions };
});
}
export async function PATCH(
request: Request,
{ params }: { params: Promise<{ key: string }> }
) {
const { key } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
const template = await updatePromptTemplate(user.id, key, body);
return { template };
});
}
export async function POST(
request: Request,
{ params }: { params: Promise<{ key: string }> }
) {
const { key } = await params;
const { searchParams } = new URL(request.url);
const action = searchParams.get("action");
const body = await request.json().catch(() => ({}));
return handleApi(async () => {
const { user } = await requireUser();
if (action === "reset") {
const template = await resetPromptTemplate(user.id, key);
return { template };
}
if (action === "preview") {
const rendered = await previewTemplate(user.id, key, body.body);
return { rendered };
}
if (action === "restore" && body.version) {
const template = await restoreTemplateVersion(user.id, key, body.version);
return { template };
}
throw new Error("Unknown action");
});
}

View File

@@ -0,0 +1,12 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { getPromptTemplates } from "@/lib/services/ai-templates";
import { PLACEHOLDER_DOCS } from "@/lib/ai/prompts/defaults";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
const templates = await getPromptTemplates(user.id);
return { templates, placeholders: PLACEHOLDER_DOCS };
});
}

View File

@@ -0,0 +1,28 @@
import { handleApi, jsonOk } from "@/lib/api";
import { getSession, verifyPassword } from "@/lib/auth";
export async function POST(request: Request) {
return handleApi(async () => {
const { password } = await request.json();
if (!verifyPassword(password)) {
throw new Error("Invalid password");
}
const session = await getSession();
session.isLoggedIn = true;
await session.save();
return { ok: true };
});
}
export async function DELETE() {
return handleApi(async () => {
const session = await getSession();
session.destroy();
return { ok: true };
});
}
export async function GET() {
const session = await getSession();
return jsonOk({ isLoggedIn: !!session.isLoggedIn });
}

View File

@@ -0,0 +1,15 @@
import { handleApi } from "@/lib/api";
import { logPages } from "@/lib/services/reading";
import { requireUser } from "@/lib/services/user";
export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return logPages(user.id, id, body.pages, body.date, body.note);
});
}

View File

@@ -0,0 +1,15 @@
import { handleApi } from "@/lib/api";
import { updateBook } from "@/lib/services/reading";
import { requireUser } from "@/lib/services/user";
export async function PATCH(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return updateBook(id, user.id, body);
});
}

View File

@@ -0,0 +1,18 @@
import { handleApi } from "@/lib/api";
import { getBooks, createBook } from "@/lib/services/reading";
import { requireUser } from "@/lib/services/user";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
return getBooks(user.id);
});
}
export async function POST(request: Request) {
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return createBook(user.id, body);
});
}

View File

@@ -0,0 +1,10 @@
import { handleApi } from "@/lib/api";
import { getCartographerDesk } from "@/lib/services/cartographer";
import { requireUser } from "@/lib/services/user";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
return getCartographerDesk(user.id);
});
}

View File

@@ -0,0 +1,42 @@
import { NextResponse } from "next/server";
import { verifyCronSecret } from "@/lib/auth";
import { requireUser } from "@/lib/services/user";
import { materializeDay } from "@/lib/services/adventure";
import { todayString } from "@/lib/dates";
import { generateDailyQuests, generateWeeklyExplorations } from "@/lib/services/explorations";
import { weekStartString } from "@/lib/dates";
import { generateWeeklyReview } from "@/lib/services/explorations";
import { pruneOldActions } from "@/lib/services/action-events";
export async function POST(request: Request) {
if (!verifyCronSecret(request)) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const { user } = await requireUser();
const { searchParams } = new URL(request.url);
const action = searchParams.get("action") ?? "materialize";
if (action === "materialize") {
await materializeDay(user.id, todayString());
return NextResponse.json({ ok: true });
}
if (action === "generate-quests") {
await generateDailyQuests(user.id);
return NextResponse.json({ ok: true });
}
if (action === "generate-explorations") {
await generateWeeklyExplorations(user.id);
return NextResponse.json({ ok: true });
}
if (action === "generate-weekly-review") {
await generateWeeklyReview(user.id, weekStartString());
return NextResponse.json({ ok: true });
}
if (action === "prune-actions") {
await pruneOldActions(user.id, 90);
return NextResponse.json({ ok: true });
}
return NextResponse.json({ error: "Unknown action" }, { status: 400 });
}

View File

@@ -0,0 +1,8 @@
import { handleApi } from "@/lib/api";
import { getDashboard } from "@/lib/services/dashboard";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const date = searchParams.get("date") ?? undefined;
return handleApi(() => getDashboard(date));
}

View File

@@ -0,0 +1,29 @@
import { handleApi } from "@/lib/api";
import {
acceptExploration,
completeExploration,
dismissExploration,
} from "@/lib/services/explorations";
import { requireUser } from "@/lib/services/user";
import { todayString } from "@/lib/dates";
export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
if (body.action === "accept") {
return acceptExploration(id, user.id);
}
if (body.action === "complete") {
return completeExploration(id, user.id, body.note ?? "", body.date ?? todayString());
}
if (body.action === "dismiss" || body.action === "deny") {
return dismissExploration(id, user.id);
}
throw new Error("Unknown action");
});
}

View File

@@ -0,0 +1,33 @@
import { handleApi } from "@/lib/api";
import {
getExplorations,
generateWeeklyExplorations,
acceptExploration,
completeExploration,
dismissExploration,
getExplorationHistory,
} from "@/lib/services/explorations";
import { requireUser } from "@/lib/services/user";
import { todayString } from "@/lib/dates";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const weekOf = searchParams.get("week_of") ?? undefined;
const history = searchParams.get("history") === "true";
return handleApi(async () => {
const { user } = await requireUser();
if (history) return getExplorationHistory(user.id);
return getExplorations(user.id, weekOf);
});
}
export async function POST(request: Request) {
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
if (body.action === "generate") {
return generateWeeklyExplorations(user.id);
}
throw new Error("Unknown action");
});
}

View File

@@ -0,0 +1,25 @@
import { eq } from "drizzle-orm";
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { db } from "@/lib/db";
import * as schema from "@adventureos/db/schema";
import { exportMemories } from "@/lib/services/ai-memory";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
const aiData = await exportMemories(user.id);
const tables = {
user: await db.select().from(schema.users).where(eq(schema.users.id, user.id)),
progress: await db.select().from(schema.userProgress),
templates: await db.select().from(schema.adventureTemplates),
books: await db.select().from(schema.books),
achievements: await db.select().from(schema.achievements),
reflections: await db.select().from(schema.reflections),
aiMemories: aiData.memories,
aiProfileSummary: aiData.summary,
aiMemoryLearning: aiData.learning,
};
return tables;
});
}

View File

@@ -0,0 +1,31 @@
import { handleApi } from "@/lib/api";
import { listCalibreBooks } from "@/lib/services/calibre";
import { getReadingProgress } from "@/lib/services/calibre-reading";
import { requireUser } from "@/lib/services/user";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const search = searchParams.get("search") ?? undefined;
const limit = Number(searchParams.get("limit") ?? "200");
const offset = Number(searchParams.get("offset") ?? "0");
return handleApi(async () => {
const { user } = await requireUser();
const books = await listCalibreBooks({ search, limit, offset });
const progress = await getReadingProgress(user.id);
const progressMap = new Map(progress.map((p) => [p.calibreBookId, p]));
return books.map((b) => {
const p = progressMap.get(b.id);
return {
...b,
currentPage: p?.currentPage ?? 0,
totalPages: p?.totalPages ?? 300,
status: p?.status ?? "unread",
progressPercent: p
? Math.round((p.currentPage / Math.max(p.totalPages, 1)) * 100)
: 0,
};
});
});
}

View File

@@ -0,0 +1,30 @@
import fs from "fs";
import { resolveCoverPath } from "@/lib/services/calibre";
import { requireUser } from "@/lib/services/user";
export async function GET(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params;
const bookId = Number(id);
if (!Number.isFinite(bookId)) {
return new Response("Invalid id", { status: 400 });
}
await requireUser();
const coverPath = resolveCoverPath(bookId);
if (!coverPath) {
return new Response("Cover not found", { status: 404 });
}
const buffer = fs.readFileSync(coverPath);
return new Response(buffer, {
headers: { "Content-Type": "image/jpeg", "Cache-Control": "private, max-age=3600" },
});
} catch (e) {
const msg = e instanceof Error ? e.message : "Error";
if (msg === "Unauthorized") return new Response(msg, { status: 401 });
return new Response(msg, { status: 500 });
}
}

View File

@@ -0,0 +1,23 @@
import { handleApi } from "@/lib/api";
import { logCalibrePages } from "@/lib/services/calibre-reading";
import { requireUser } from "@/lib/services/user";
import { todayString } from "@/lib/dates";
export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const calibreBookId = Number(id);
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return logCalibrePages(
user.id,
calibreBookId,
body.pages ?? 10,
body.date ?? todayString()
);
});
}

View File

@@ -0,0 +1,10 @@
import { handleApi } from "@/lib/api";
import { getCalibreStatus } from "@/lib/services/calibre";
import { requireUser } from "@/lib/services/user";
export async function GET() {
return handleApi(async () => {
await requireUser();
return getCalibreStatus();
});
}

View File

@@ -0,0 +1,26 @@
import { handleApi } from "@/lib/api";
import { saveReflection, getReflection } from "@/lib/services/reflection";
import { requireUser } from "@/lib/services/user";
export async function POST(
request: Request,
{ params }: { params: Promise<{ date: string }> }
) {
const { date } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
return saveReflection(user.id, date, body);
});
}
export async function GET(
_request: Request,
{ params }: { params: Promise<{ date: string }> }
) {
const { date } = await params;
return handleApi(async () => {
const { user } = await requireUser();
return getReflection(user.id, date);
});
}

View File

@@ -0,0 +1,37 @@
import { handleApi } from "@/lib/api";
import {
getWeeklyReview,
generateWeeklyReview,
setReviewIntention,
} from "@/lib/services/explorations";
import { requireUser } from "@/lib/services/user";
export async function GET(
_request: Request,
{ params }: { params: Promise<{ weekStart: string }> }
) {
const { weekStart } = await params;
return handleApi(async () => {
const { user } = await requireUser();
return getWeeklyReview(user.id, weekStart);
});
}
export async function POST(
request: Request,
{ params }: { params: Promise<{ weekStart: string }> }
) {
const { weekStart } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
if (body.action === "generate") {
return generateWeeklyReview(user.id, weekStart);
}
if (body.intention) {
await setReviewIntention(user.id, weekStart, body.intention);
return { ok: true };
}
throw new Error("Unknown action");
});
}

View File

@@ -0,0 +1,20 @@
import { handleApi } from "@/lib/api";
import { requireUser } from "@/lib/services/user";
import { getDayBoundaryHour, setDayBoundaryHour } from "@/lib/services/day-boundary";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
const hour = await getDayBoundaryHour(user.id);
return { hour };
});
}
export async function PATCH(request: Request) {
return handleApi(async () => {
const { user } = await requireUser();
const body = await request.json();
const hour = await setDayBoundaryHour(user.id, body.hour ?? 0);
return { hour };
});
}

View File

@@ -0,0 +1,58 @@
import { handleApi } from "@/lib/api";
import { requireUser, updateUserProfile } from "@/lib/services/user";
import { db, spiritualConfig } from "@/lib/db";
import { eq } from "drizzle-orm";
import { normalizeSettingsTheme } from "@/lib/services/theme-migration";
import { migrateUserThemeInDb } from "@/lib/services/theme-migration";
import { seedPromptTemplatesForUser } from "@/lib/services/ai-templates";
import { getSettingsForUser, upsertSetting } from "@/lib/repositories/settings.repository";
import { parseJsonBody, settingsPatchSchema } from "@/lib/validation";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
const allSettings = await getSettingsForUser(user.id);
const settingsMap = Object.fromEntries(allSettings.map((s) => [s.key, s.value]));
const migratedTheme = await migrateUserThemeInDb(
user.id,
settingsMap.theme as string | undefined
);
settingsMap.theme = migratedTheme;
const [spiritual] = await db
.select()
.from(spiritualConfig)
.where(eq(spiritualConfig.userId, user.id));
await seedPromptTemplatesForUser(user.id);
return {
user,
settings: normalizeSettingsTheme(settingsMap),
spiritual,
};
});
}
export async function PATCH(request: Request) {
return handleApi(async () => {
const body = await parseJsonBody(request, settingsPatchSchema);
const { user } = await requireUser();
if (body.profile) {
await updateUserProfile(user.id, body.profile);
}
if (body.settings) {
for (const [key, value] of Object.entries(body.settings)) {
await upsertSetting(user.id, key, value);
}
}
if (body.spiritual) {
await db
.update(spiritualConfig)
.set({
prayerLabels: body.spiritual.prayerLabels,
litanyLabels: body.spiritual.litanyLabels,
})
.where(eq(spiritualConfig.userId, user.id));
}
return { ok: true };
});
}

View File

@@ -0,0 +1,15 @@
import { handleApi } from "@/lib/api";
import { getStatsOverview, getStatsDomain } from "@/lib/services/dashboard";
export async function GET(
request: Request,
{ params }: { params: Promise<{ domain: string }> }
) {
const { domain } = await params;
const { searchParams } = new URL(request.url);
const range = Number(searchParams.get("range") ?? 30);
return handleApi(async () => {
if (domain === "overview") return getStatsOverview();
return getStatsDomain(domain, range);
});
}

View File

@@ -0,0 +1,31 @@
import { handleApi } from "@/lib/api";
import { getTeacherLesson, completeTeacherLesson } from "@/lib/services/teacher";
import { requireUser } from "@/lib/services/user";
export async function GET(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
return handleApi(async () => {
const { user } = await requireUser();
const lesson = await getTeacherLesson(user.id, id);
if (!lesson) throw new Error("Lesson not found");
return lesson;
});
}
export async function PATCH(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
if (body.action === "complete") {
return completeTeacherLesson(user.id, id, body.completedNote ?? "");
}
throw new Error("Unknown action");
});
}

View File

@@ -0,0 +1,34 @@
import { handleApi } from "@/lib/api";
import { createTeacherLesson, getTeacherHistory } from "@/lib/services/teacher";
import { requireUser } from "@/lib/services/user";
import { parseJsonBody } from "@/lib/validation";
import {
teacherCreateSchema,
normalizeTeacherCreateBody,
} from "@/lib/validation/schemas";
export async function POST(request: Request) {
return handleApi(async () => {
// #region agent log
fetch('http://127.0.0.1:7257/ingest/abfa22e4-3d86-4176-9679-7c59b1f0489f',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'04e12c'},body:JSON.stringify({sessionId:'04e12c',location:'api/teacher/route.ts:POST',message:'teacher POST start',data:{},timestamp:Date.now(),hypothesisId:'H1'})}).catch(()=>{});
// #endregion
const parsed = await parseJsonBody(request, teacherCreateSchema);
const body = normalizeTeacherCreateBody(parsed);
// #region agent log
fetch('http://127.0.0.1:7257/ingest/abfa22e4-3d86-4176-9679-7c59b1f0489f',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'04e12c'},body:JSON.stringify({sessionId:'04e12c',location:'api/teacher/route.ts:POST',message:'teacher body validated',data:{topicLen:body.topic.length,hasExplorationId:!!body.explorationId},timestamp:Date.now(),hypothesisId:'H1'})}).catch(()=>{});
// #endregion
const { user } = await requireUser();
const result = await createTeacherLesson(user.id, body);
// #region agent log
fetch('http://127.0.0.1:7257/ingest/abfa22e4-3d86-4176-9679-7c59b1f0489f',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'04e12c'},body:JSON.stringify({sessionId:'04e12c',location:'api/teacher/route.ts:POST',message:'teacher lesson created',data:{source:result.source,lessonId:result.id},timestamp:Date.now(),hypothesisId:'H2'})}).catch(()=>{});
// #endregion
return result;
});
}
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
return getTeacherHistory(user.id);
});
}

View File

@@ -0,0 +1,32 @@
import { handleApi } from "@/lib/api";
import { upsertTemplateItem, deleteTemplateItem, verifyTemplateOwnership } from "@/lib/services/adventure";
import { requireUser } from "@/lib/services/user";
export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
await verifyTemplateOwnership(user.id, id);
const itemId = await upsertTemplateItem(id, body);
return { id: itemId };
});
}
export async function DELETE(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const { searchParams } = new URL(request.url);
const itemId = searchParams.get("itemId");
if (!itemId) throw new Error("itemId required");
return handleApi(async () => {
const { user } = await requireUser();
const result = await deleteTemplateItem(itemId, user.id);
return { ok: true, ...result };
});
}

View File

@@ -0,0 +1,47 @@
import { handleApi } from "@/lib/api";
import {
updateTemplate,
deleteTemplate,
duplicateTemplate,
verifyTemplateOwnership,
} from "@/lib/services/adventure";
import { requireUser } from "@/lib/services/user";
export async function PATCH(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
await verifyTemplateOwnership(user.id, id);
return updateTemplate(user.id, id, body);
});
}
export async function DELETE(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
return handleApi(async () => {
const { user } = await requireUser();
return deleteTemplate(user.id, id);
});
}
export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json();
return handleApi(async () => {
const { user } = await requireUser();
if (body.action === "duplicate") {
return duplicateTemplate(user.id, id);
}
throw new Error("Unknown action");
});
}

View File

@@ -0,0 +1,19 @@
import { handleApi } from "@/lib/api";
import { getTemplates, createTemplate } from "@/lib/services/adventure";
import { requireUser } from "@/lib/services/user";
import { parseJsonBody, templateCreateSchema } from "@/lib/validation";
export async function GET() {
return handleApi(async () => {
const { user } = await requireUser();
return getTemplates(user.id);
});
}
export async function POST(request: Request) {
return handleApi(async () => {
const body = await parseJsonBody(request, templateCreateSchema);
const { user } = await requireUser();
return createTemplate(user.id, body);
});
}

View File

@@ -0,0 +1,6 @@
import { jsonOk } from "@/lib/api";
import { THEMES } from "@/themes/registry";
export async function GET() {
return jsonOk({ themes: THEMES });
}