Skip to content

Commit bcd93cb

Browse files
committed
refactor(llm): extract controllers from routes + human review
- Create dedicated controllers in /src/adapters/controllers/llm/ - stream-completion.controller.ts - send-chat-message.controller.ts - get-conversation.controller.ts - list-conversations.controller.ts - delete-conversation.controller.ts - list-messages.controller.ts - get-usage-stats.controller.ts - check-budget.controller.ts - Create thin route delegation in /app/api/llm/ - conversations/route.ts (GET, POST) - conversations/[id]/route.ts (GET, DELETE) - conversations/[id]/messages/route.ts (GET) - usage/route.ts (GET) - budget/route.ts (GET) - Fix transaction handling in deleteConversationAction - Add ITransactionManagerService to DI_SYMBOLS - Human review and refactoring pass
1 parent 26750f2 commit bcd93cb

70 files changed

Lines changed: 926 additions & 783 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/nextjs/app/(protected)/_components/sidebar.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
LayoutDashboard,
99
MessageSquare,
1010
Settings,
11-
Users,
1211
} from "lucide-react";
1312
import Link from "next/link";
1413
import { usePathname } from "next/navigation";
@@ -18,7 +17,6 @@ const navigation = [
1817
{ name: "Chat", href: "/chat", icon: MessageSquare },
1918
{ name: "Prompts", href: "/admin/prompts", icon: FileText },
2019
{ name: "Usage", href: "/admin/usage", icon: BarChart3 },
21-
{ name: "Team", href: "/team", icon: Users },
2220
{ name: "Billing", href: "/settings/billing", icon: CreditCard },
2321
{ name: "Settings", href: "/settings", icon: Settings },
2422
];
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { checkBudgetController } from "@/adapters/controllers/llm/check-budget.controller";
2+
3+
export async function GET(request: Request) {
4+
return checkBudgetController(request);
5+
}
Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,5 @@
1-
import { NextResponse } from "next/server";
2-
import { authGuard } from "@/adapters/guards/auth.guard";
3-
import { streamCompletionInputDtoSchema } from "@/application/dto/llm/stream-completion.dto";
4-
import { getInjection } from "@/common/di/container";
1+
import { streamCompletionController } from "@/adapters/controllers/llm/stream-completion.controller";
52

63
export async function POST(request: Request) {
7-
const guardResult = await authGuard();
8-
9-
if (!guardResult.authenticated) {
10-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
11-
}
12-
13-
const body = await request.json();
14-
const parseResult = streamCompletionInputDtoSchema.safeParse(body);
15-
16-
if (!parseResult.success) {
17-
return NextResponse.json(
18-
{ error: parseResult.error.issues[0]?.message ?? "Invalid input" },
19-
{ status: 400 },
20-
);
21-
}
22-
23-
const useCase = getInjection("StreamCompletionUseCase");
24-
const result = await useCase.execute({
25-
...parseResult.data,
26-
userId: guardResult.session.user.id,
27-
});
28-
29-
if (result.isFailure) {
30-
return NextResponse.json({ error: result.getError() }, { status: 400 });
31-
}
32-
33-
const { stream, model, provider } = result.getValue();
34-
35-
return new Response(stream, {
36-
headers: {
37-
"Content-Type": "text/plain; charset=utf-8",
38-
"Transfer-Encoding": "chunked",
39-
"X-LLM-Model": model,
40-
"X-LLM-Provider": provider,
41-
},
42-
});
4+
return streamCompletionController(request);
435
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { listMessagesController } from "@/adapters/controllers/llm/list-messages.controller";
2+
3+
interface RouteParams {
4+
params: Promise<{ id: string }>;
5+
}
6+
7+
export async function GET(request: Request, { params }: RouteParams) {
8+
const { id } = await params;
9+
return listMessagesController(request, id);
10+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { deleteConversationController } from "@/adapters/controllers/llm/delete-conversation.controller";
2+
import { getConversationController } from "@/adapters/controllers/llm/get-conversation.controller";
3+
4+
interface RouteParams {
5+
params: Promise<{ id: string }>;
6+
}
7+
8+
export async function GET(request: Request, { params }: RouteParams) {
9+
const { id } = await params;
10+
return getConversationController(request, id);
11+
}
12+
13+
export async function DELETE(request: Request, { params }: RouteParams) {
14+
const { id } = await params;
15+
return deleteConversationController(request, id);
16+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { listConversationsController } from "@/adapters/controllers/llm/list-conversations.controller";
2+
import { sendChatMessageController } from "@/adapters/controllers/llm/send-chat-message.controller";
3+
4+
export async function GET(request: Request) {
5+
return listConversationsController(request);
6+
}
7+
8+
export async function POST(request: Request) {
9+
return sendChatMessageController(request);
10+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { getUsageStatsController } from "@/adapters/controllers/llm/get-usage-stats.controller";
2+
3+
export async function GET(request: Request) {
4+
return getUsageStatsController(request);
5+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { createModule } from "@evyweb/ioctopus";
2+
import { TransactionService } from "@packages/drizzle";
3+
import { env } from "@/common/env";
4+
import { DI_SYMBOLS } from "../types";
5+
6+
export const createTransactionModule = () => {
7+
const transactionModule = createModule();
8+
if (env.NODE_ENV === "test") {
9+
} else {
10+
transactionModule
11+
.bind(DI_SYMBOLS.ITransactionManagerService)
12+
.toClass(TransactionService);
13+
}
14+
15+
return transactionModule;
16+
};

apps/nextjs/common/di/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ITransactionManagerService } from "@packages/drizzle/services/transaction-manager.type";
12
import type { IAuthProvider } from "@/application/ports/auth.service.port";
23
import type { IConversationRepository } from "@/application/ports/conversation.repository.port";
34
import type { IEmailService } from "@/application/ports/email.service.port";
@@ -74,6 +75,7 @@ export const DI_SYMBOLS = {
7475
EstimateCostUseCase: Symbol.for("EstimateCostUseCase"),
7576
GetUsageStatsUseCase: Symbol.for("GetUsageStatsUseCase"),
7677
CheckBudgetUseCase: Symbol.for("CheckBudgetUseCase"),
78+
ITransactionManagerService: Symbol.for("ITransactionManagerService"),
7779
};
7880

7981
export interface DI_RETURN_TYPES {
@@ -114,4 +116,5 @@ export interface DI_RETURN_TYPES {
114116
EstimateCostUseCase: EstimateCostUseCase;
115117
GetUsageStatsUseCase: GetUsageStatsUseCase;
116118
CheckBudgetUseCase: CheckBudgetUseCase;
119+
ITransactionManagerService: ITransactionManagerService;
117120
}

apps/nextjs/src/__TESTS__/application/llm/delete-conversation-use-case.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { IEventDispatcher } from "@/application/ports/event-dispatcher.port
66
import { DeleteConversationUseCase } from "@/application/use-cases/llm/delete-conversation.use-case";
77
import { Conversation } from "@/domain/llm/conversation/conversation.aggregate";
88
import { ConversationDeletedEvent } from "@/domain/llm/conversation/events/conversation-deleted.event";
9+
import { UserId } from "@/domain/user/user-id";
910

1011
describe("DeleteConversationUseCase", () => {
1112
let useCase: DeleteConversationUseCase;
@@ -23,7 +24,7 @@ describe("DeleteConversationUseCase", () => {
2324
const createMockConversation = (ownerId: string = userId): Conversation => {
2425
return Conversation.create(
2526
{
26-
userId: ownerId,
27+
userId: UserId.create(new UUID(ownerId)),
2728
title: Option.none(),
2829
metadata: Option.none(),
2930
},

0 commit comments

Comments
 (0)