23
apps/web/src/app/api/achievements/route.ts
Executable file
23
apps/web/src/app/api/achievements/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
16
apps/web/src/app/api/actions/[id]/undo/route.ts
Executable file
16
apps/web/src/app/api/actions/[id]/undo/route.ts
Executable 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;
|
||||
});
|
||||
}
|
||||
11
apps/web/src/app/api/actions/recent/route.ts
Executable file
11
apps/web/src/app/api/actions/recent/route.ts
Executable 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 };
|
||||
});
|
||||
}
|
||||
15
apps/web/src/app/api/adventures/[date]/apply-template/route.ts
Executable file
15
apps/web/src/app/api/adventures/[date]/apply-template/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
29
apps/web/src/app/api/adventures/[date]/items/[id]/route.ts
Executable file
29
apps/web/src/app/api/adventures/[date]/items/[id]/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
15
apps/web/src/app/api/adventures/[date]/items/route.ts
Executable file
15
apps/web/src/app/api/adventures/[date]/items/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
15
apps/web/src/app/api/adventures/[date]/quick-log/route.ts
Normal file
15
apps/web/src/app/api/adventures/[date]/quick-log/route.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
15
apps/web/src/app/api/adventures/[date]/rest-day/route.ts
Executable file
15
apps/web/src/app/api/adventures/[date]/rest-day/route.ts
Executable 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 };
|
||||
});
|
||||
}
|
||||
32
apps/web/src/app/api/adventures/[date]/route.ts
Executable file
32
apps/web/src/app/api/adventures/[date]/route.ts
Executable 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");
|
||||
});
|
||||
}
|
||||
26
apps/web/src/app/api/adventures/[date]/todos/[id]/route.ts
Executable file
26
apps/web/src/app/api/adventures/[date]/todos/[id]/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
15
apps/web/src/app/api/adventures/[date]/todos/route.ts
Executable file
15
apps/web/src/app/api/adventures/[date]/todos/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
11
apps/web/src/app/api/adventures/catch-up/route.ts
Normal file
11
apps/web/src/app/api/adventures/catch-up/route.ts
Normal 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 };
|
||||
});
|
||||
}
|
||||
10
apps/web/src/app/api/ai/chat/knows/route.ts
Normal file
10
apps/web/src/app/api/ai/chat/knows/route.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
40
apps/web/src/app/api/ai/chat/sessions/[id]/route.ts
Normal file
40
apps/web/src/app/api/ai/chat/sessions/[id]/route.ts
Normal 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 ?? "");
|
||||
});
|
||||
}
|
||||
19
apps/web/src/app/api/ai/chat/sessions/route.ts
Normal file
19
apps/web/src/app/api/ai/chat/sessions/route.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
33
apps/web/src/app/api/ai/config/route.ts
Executable file
33
apps/web/src/app/api/ai/config/route.ts
Executable 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 };
|
||||
});
|
||||
}
|
||||
19
apps/web/src/app/api/ai/context/preview/route.ts
Normal file
19
apps/web/src/app/api/ai/context/preview/route.ts
Normal 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),
|
||||
};
|
||||
});
|
||||
}
|
||||
21
apps/web/src/app/api/ai/health/route.ts
Executable file
21
apps/web/src/app/api/ai/health/route.ts
Executable 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 };
|
||||
});
|
||||
}
|
||||
40
apps/web/src/app/api/ai/memory/[id]/route.ts
Normal file
40
apps/web/src/app/api/ai/memory/[id]/route.ts
Normal 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 };
|
||||
});
|
||||
}
|
||||
21
apps/web/src/app/api/ai/memory/learning/route.ts
Normal file
21
apps/web/src/app/api/ai/memory/learning/route.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
13
apps/web/src/app/api/ai/memory/reset/route.ts
Normal file
13
apps/web/src/app/api/ai/memory/reset/route.ts
Normal 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 };
|
||||
});
|
||||
}
|
||||
32
apps/web/src/app/api/ai/memory/route.ts
Normal file
32
apps/web/src/app/api/ai/memory/route.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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 };
|
||||
});
|
||||
}
|
||||
@@ -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 };
|
||||
});
|
||||
}
|
||||
13
apps/web/src/app/api/ai/memory/suggestions/route.ts
Normal file
13
apps/web/src/app/api/ai/memory/suggestions/route.ts
Normal 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 };
|
||||
});
|
||||
}
|
||||
54
apps/web/src/app/api/ai/memory/summary/rebuild/route.ts
Normal file
54
apps/web/src/app/api/ai/memory/summary/rebuild/route.ts
Normal 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 };
|
||||
});
|
||||
}
|
||||
34
apps/web/src/app/api/ai/memory/summary/route.ts
Normal file
34
apps/web/src/app/api/ai/memory/summary/route.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
23
apps/web/src/app/api/ai/models/route.ts
Executable file
23
apps/web/src/app/api/ai/models/route.ts
Executable 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 };
|
||||
});
|
||||
}
|
||||
15
apps/web/src/app/api/ai/suggestions/[id]/dismiss/route.ts
Executable file
15
apps/web/src/app/api/ai/suggestions/[id]/dismiss/route.ts
Executable 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 };
|
||||
});
|
||||
}
|
||||
26
apps/web/src/app/api/ai/suggestions/route.ts
Executable file
26
apps/web/src/app/api/ai/suggestions/route.ts
Executable 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");
|
||||
});
|
||||
}
|
||||
64
apps/web/src/app/api/ai/templates/[key]/route.ts
Executable file
64
apps/web/src/app/api/ai/templates/[key]/route.ts
Executable 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");
|
||||
});
|
||||
}
|
||||
12
apps/web/src/app/api/ai/templates/route.ts
Executable file
12
apps/web/src/app/api/ai/templates/route.ts
Executable 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 };
|
||||
});
|
||||
}
|
||||
28
apps/web/src/app/api/auth/login/route.ts
Executable file
28
apps/web/src/app/api/auth/login/route.ts
Executable 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 });
|
||||
}
|
||||
15
apps/web/src/app/api/books/[id]/log-pages/route.ts
Executable file
15
apps/web/src/app/api/books/[id]/log-pages/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
15
apps/web/src/app/api/books/[id]/route.ts
Executable file
15
apps/web/src/app/api/books/[id]/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
18
apps/web/src/app/api/books/route.ts
Executable file
18
apps/web/src/app/api/books/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
10
apps/web/src/app/api/cartographer/route.ts
Executable file
10
apps/web/src/app/api/cartographer/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
42
apps/web/src/app/api/cron/route.ts
Executable file
42
apps/web/src/app/api/cron/route.ts
Executable 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 });
|
||||
}
|
||||
8
apps/web/src/app/api/dashboard/route.ts
Executable file
8
apps/web/src/app/api/dashboard/route.ts
Executable 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));
|
||||
}
|
||||
29
apps/web/src/app/api/explorations/[id]/route.ts
Executable file
29
apps/web/src/app/api/explorations/[id]/route.ts
Executable 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");
|
||||
});
|
||||
}
|
||||
33
apps/web/src/app/api/explorations/route.ts
Executable file
33
apps/web/src/app/api/explorations/route.ts
Executable 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");
|
||||
});
|
||||
}
|
||||
25
apps/web/src/app/api/export/json/route.ts
Executable file
25
apps/web/src/app/api/export/json/route.ts
Executable 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;
|
||||
});
|
||||
}
|
||||
31
apps/web/src/app/api/library/books/route.ts
Executable file
31
apps/web/src/app/api/library/books/route.ts
Executable 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,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
30
apps/web/src/app/api/library/cover/[id]/route.ts
Executable file
30
apps/web/src/app/api/library/cover/[id]/route.ts
Executable 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 });
|
||||
}
|
||||
}
|
||||
23
apps/web/src/app/api/library/progress/[id]/route.ts
Executable file
23
apps/web/src/app/api/library/progress/[id]/route.ts
Executable 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()
|
||||
);
|
||||
});
|
||||
}
|
||||
10
apps/web/src/app/api/library/status/route.ts
Executable file
10
apps/web/src/app/api/library/status/route.ts
Executable 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();
|
||||
});
|
||||
}
|
||||
26
apps/web/src/app/api/reflections/[date]/route.ts
Executable file
26
apps/web/src/app/api/reflections/[date]/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
37
apps/web/src/app/api/reviews/[weekStart]/route.ts
Executable file
37
apps/web/src/app/api/reviews/[weekStart]/route.ts
Executable 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");
|
||||
});
|
||||
}
|
||||
20
apps/web/src/app/api/settings/day-boundary/route.ts
Normal file
20
apps/web/src/app/api/settings/day-boundary/route.ts
Normal 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 };
|
||||
});
|
||||
}
|
||||
58
apps/web/src/app/api/settings/route.ts
Executable file
58
apps/web/src/app/api/settings/route.ts
Executable 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 };
|
||||
});
|
||||
}
|
||||
15
apps/web/src/app/api/stats/[domain]/route.ts
Executable file
15
apps/web/src/app/api/stats/[domain]/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
31
apps/web/src/app/api/teacher/[id]/route.ts
Executable file
31
apps/web/src/app/api/teacher/[id]/route.ts
Executable 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");
|
||||
});
|
||||
}
|
||||
34
apps/web/src/app/api/teacher/route.ts
Executable file
34
apps/web/src/app/api/teacher/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
32
apps/web/src/app/api/templates/[id]/items/route.ts
Executable file
32
apps/web/src/app/api/templates/[id]/items/route.ts
Executable 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 };
|
||||
});
|
||||
}
|
||||
47
apps/web/src/app/api/templates/[id]/route.ts
Executable file
47
apps/web/src/app/api/templates/[id]/route.ts
Executable 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");
|
||||
});
|
||||
}
|
||||
19
apps/web/src/app/api/templates/route.ts
Executable file
19
apps/web/src/app/api/templates/route.ts
Executable 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);
|
||||
});
|
||||
}
|
||||
6
apps/web/src/app/api/themes/route.ts
Executable file
6
apps/web/src/app/api/themes/route.ts
Executable file
@@ -0,0 +1,6 @@
|
||||
import { jsonOk } from "@/lib/api";
|
||||
import { THEMES } from "@/themes/registry";
|
||||
|
||||
export async function GET() {
|
||||
return jsonOk({ themes: THEMES });
|
||||
}
|
||||
Reference in New Issue
Block a user