mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-02 10:35:37 +08:00
refactor: move assets to its own service
This commit is contained in:
74
server/src/modules/assets/assets.routes.ts
Normal file
74
server/src/modules/assets/assets.routes.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import express, { type NextFunction, type Request, type Response } from 'express';
|
||||||
|
import multer from 'multer';
|
||||||
|
|
||||||
|
import { asyncHandler } from '@/shared/http/async-handler.js';
|
||||||
|
import { AppError } from '@/shared/utils/app-error.js';
|
||||||
|
import { createApiErrorResponse, createApiSuccessResponse } from '@/shared/http/api-response.js';
|
||||||
|
import { llmAssetsService } from '@/modules/assets/assets.service.js';
|
||||||
|
import { logger } from '@/shared/utils/logger.js';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
const upload = multer({
|
||||||
|
storage: multer.memoryStorage(),
|
||||||
|
limits: {
|
||||||
|
files: 10,
|
||||||
|
fileSize: 20 * 1024 * 1024,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads optional query/body values and trims surrounding whitespace.
|
||||||
|
*/
|
||||||
|
const readOptionalQueryString = (value: unknown): string | undefined => {
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = value.trim();
|
||||||
|
return normalized.length > 0 ? normalized : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads one or more images into `.cloudcli/assets` so providers can reuse file paths.
|
||||||
|
*/
|
||||||
|
router.post(
|
||||||
|
'/images',
|
||||||
|
upload.array('images', 10),
|
||||||
|
asyncHandler(async (req: Request, res: Response) => {
|
||||||
|
const workspacePath = readOptionalQueryString((req.body as Record<string, unknown> | undefined)?.workspacePath);
|
||||||
|
const filesValue = (req as Request & { files?: unknown }).files;
|
||||||
|
const files = Array.isArray(filesValue) ? filesValue as Array<{
|
||||||
|
originalname: string;
|
||||||
|
mimetype: string;
|
||||||
|
size: number;
|
||||||
|
buffer: Buffer;
|
||||||
|
}> : [];
|
||||||
|
const images = await llmAssetsService.storeUploadedImages(files, { workspacePath });
|
||||||
|
res.status(201).json(createApiSuccessResponse({ images }));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes route-level failures to a consistent JSON API shape.
|
||||||
|
*/
|
||||||
|
router.use((error: unknown, _req: Request, res: Response, _next: NextFunction) => {
|
||||||
|
if (res.headersSent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof AppError) {
|
||||||
|
res
|
||||||
|
.status(error.statusCode)
|
||||||
|
.json(createApiErrorResponse(error.code, error.message, undefined, error.details));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = error instanceof Error ? error.message : 'Unexpected assets route failure.';
|
||||||
|
logger.error(message, {
|
||||||
|
module: 'assets.routes',
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(500).json(createApiErrorResponse('INTERNAL_ERROR', message));
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import express, { type NextFunction, type Request, type Response } from 'express';
|
import express, { type NextFunction, type Request, type Response } from 'express';
|
||||||
import multer from 'multer';
|
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
import { asyncHandler } from '@/shared/http/async-handler.js';
|
import { asyncHandler } from '@/shared/http/async-handler.js';
|
||||||
@@ -7,7 +6,6 @@ import { AppError } from '@/shared/utils/app-error.js';
|
|||||||
import { createApiErrorResponse, createApiSuccessResponse } from '@/shared/http/api-response.js';
|
import { createApiErrorResponse, createApiSuccessResponse } from '@/shared/http/api-response.js';
|
||||||
import { llmService } from '@/modules/llm/services/llm.service.js';
|
import { llmService } from '@/modules/llm/services/llm.service.js';
|
||||||
import { llmSessionsService } from '@/modules/llm/services/sessions.service.js';
|
import { llmSessionsService } from '@/modules/llm/services/sessions.service.js';
|
||||||
import { llmAssetsService } from '@/modules/llm/services/assets.service.js';
|
|
||||||
import type { McpScope, McpTransport, UpsertMcpServerInput } from '@/modules/llm/services/mcp.service.js';
|
import type { McpScope, McpTransport, UpsertMcpServerInput } from '@/modules/llm/services/mcp.service.js';
|
||||||
import { llmMcpService } from '@/modules/llm/services/mcp.service.js';
|
import { llmMcpService } from '@/modules/llm/services/mcp.service.js';
|
||||||
import { llmSkillsService } from '@/modules/llm/services/skills.service.js';
|
import { llmSkillsService } from '@/modules/llm/services/skills.service.js';
|
||||||
@@ -16,13 +14,6 @@ import type { LLMProvider } from '@/shared/types/app.js';
|
|||||||
import { logger } from '@/shared/utils/logger.js';
|
import { logger } from '@/shared/utils/logger.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const upload = multer({
|
|
||||||
storage: multer.memoryStorage(),
|
|
||||||
limits: {
|
|
||||||
files: 10,
|
|
||||||
fileSize: 20 * 1024 * 1024,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Safely reads an Express path parameter that may arrive as string or string[].
|
* Safely reads an Express path parameter that may arrive as string or string[].
|
||||||
@@ -305,26 +296,6 @@ router.post(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads one or more images into `.cloudcli/assets` so providers can reuse file paths.
|
|
||||||
*/
|
|
||||||
router.post(
|
|
||||||
'/assets/images',
|
|
||||||
upload.array('images', 10),
|
|
||||||
asyncHandler(async (req: Request, res: Response) => {
|
|
||||||
const workspacePath = readOptionalQueryString((req.body as Record<string, unknown> | undefined)?.workspacePath);
|
|
||||||
const filesValue = (req as Request & { files?: unknown }).files;
|
|
||||||
const files = Array.isArray(filesValue) ? filesValue as Array<{
|
|
||||||
originalname: string;
|
|
||||||
mimetype: string;
|
|
||||||
size: number;
|
|
||||||
buffer: Buffer;
|
|
||||||
}> : [];
|
|
||||||
const images = await llmAssetsService.storeUploadedImages(files, { workspacePath });
|
|
||||||
res.status(201).json(createApiSuccessResponse({ images }));
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists MCP servers for one provider grouped by user/local/project scopes.
|
* Lists MCP servers for one provider grouped by user/local/project scopes.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ export async function findFilesRecursivelyCreatedAfter(
|
|||||||
fileList: string[] = [],
|
fileList: string[] = [],
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
try {
|
try {
|
||||||
console.log("HEY THERE!")
|
|
||||||
const entries = await fsp.readdir(rootDir, { withFileTypes: true });
|
const entries = await fsp.readdir(rootDir, { withFileTypes: true });
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const fullPath = path.join(rootDir, entry.name);
|
const fullPath = path.join(rootDir, entry.name);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import path from 'node:path';
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
import { AppError } from '@/shared/utils/app-error.js';
|
import { AppError } from '@/shared/utils/app-error.js';
|
||||||
import { llmAssetsService } from '@/modules/llm/services/assets.service.js';
|
import { llmAssetsService } from '@/modules/assets/assets.service.js';
|
||||||
import { ClaudeProvider } from '@/modules/llm/providers/claude.provider.js';
|
import { ClaudeProvider } from '@/modules/llm/providers/claude.provider.js';
|
||||||
import { CodexProvider } from '@/modules/llm/providers/codex.provider.js';
|
import { CodexProvider } from '@/modules/llm/providers/codex.provider.js';
|
||||||
import { CursorProvider } from '@/modules/llm/providers/cursor.provider.js';
|
import { CursorProvider } from '@/modules/llm/providers/cursor.provider.js';
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ const [
|
|||||||
projectsInlineRoutes,
|
projectsInlineRoutes,
|
||||||
filesRoutes,
|
filesRoutes,
|
||||||
llmRoutes,
|
llmRoutes,
|
||||||
|
assetsRoutes,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
importRoute('./modules/health/health.routes.js'),
|
importRoute('./modules/health/health.routes.js'),
|
||||||
importRoute('./modules/system/system.routes.js'),
|
importRoute('./modules/system/system.routes.js'),
|
||||||
@@ -92,6 +93,7 @@ const [
|
|||||||
importRoute('./modules/projects/projects.inline.routes.js'),
|
importRoute('./modules/projects/projects.inline.routes.js'),
|
||||||
importRoute('./modules/files/files.routes.js'),
|
importRoute('./modules/files/files.routes.js'),
|
||||||
importRoute('./modules/llm/llm.routes.js'),
|
importRoute('./modules/llm/llm.routes.js'),
|
||||||
|
importRoute('./modules/assets/assets.routes.js'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ---------- MIDDLEWARES ----------------
|
// ---------- MIDDLEWARES ----------------
|
||||||
@@ -187,6 +189,9 @@ app.use('/api/workspaces', authenticateToken, workspacesRoutes);
|
|||||||
// Unified LLM provider API routes (protected)
|
// Unified LLM provider API routes (protected)
|
||||||
app.use('/api/llm', authenticateToken, llmRoutes);
|
app.use('/api/llm', authenticateToken, llmRoutes);
|
||||||
|
|
||||||
|
// Shared assets API routes (protected)
|
||||||
|
app.use('/api/assets', authenticateToken, assetsRoutes);
|
||||||
|
|
||||||
// Agent API Routes (uses API key authentication)
|
// Agent API Routes (uses API key authentication)
|
||||||
app.use('/api/agent', agentRoutes);
|
app.use('/api/agent', agentRoutes);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user