- 新增 materials 表(id/projectId/description/associatedDate/status/createdAt/updatedAt) - 新增 4 个后端 API 路由(list/create/get/delete)+ 13 个测试 - 新增 use-materials hooks(TanStack Query) - 收集箱页面重构为三层架构(InboxPage + MaterialSidebar + MaterialDetailPanel) - MaterialCard: Popconfirm 删除确认 + 粗粒度时间格式 - MaterialContent: 展示状态标签 + createdAt - 更新开发文档 backend.md / frontend.md
340 lines
10 KiB
TypeScript
340 lines
10 KiB
TypeScript
import type Database from "bun:sqlite";
|
|
|
|
import type { RuntimeMode } from "../shared/api";
|
|
import type { Logger } from "./logger";
|
|
import type { StaticAssets } from "./static";
|
|
|
|
import { createApiError, jsonResponse } from "./helpers";
|
|
import { withErrorHandler } from "./middleware";
|
|
import { handleMeta } from "./routes/meta";
|
|
import { serveStaticAsset } from "./static";
|
|
import { readAppVersion } from "./version";
|
|
|
|
export interface StartServerOptions {
|
|
config: { host: string; port: number };
|
|
db: Database;
|
|
logger: Logger;
|
|
mode: RuntimeMode;
|
|
staticAssets?: StaticAssets;
|
|
version?: string;
|
|
}
|
|
|
|
export function startServer(options: StartServerOptions) {
|
|
const { config, db, logger, mode, staticAssets, version } = options;
|
|
|
|
const resolveVersion = (): Promise<string> => {
|
|
if (version) return Promise.resolve(version);
|
|
return readAppVersion();
|
|
};
|
|
|
|
const server = Bun.serve({
|
|
fetch(req) {
|
|
if (staticAssets) {
|
|
return serveStaticAsset(new URL(req.url).pathname, staticAssets);
|
|
}
|
|
return new Response("Frontend is served by Vite dev server on :5173", { status: 404 });
|
|
},
|
|
hostname: config.host,
|
|
port: config.port,
|
|
routes: {
|
|
"/api/*": () => jsonResponse(createApiError("API route not found", 404), { mode, status: 404 }),
|
|
"/api/meta": {
|
|
GET: withErrorHandler(
|
|
async () => {
|
|
const resolvedVersion = await resolveVersion();
|
|
return handleMeta(mode, resolvedVersion, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/models": {
|
|
GET: withErrorHandler(
|
|
async (req) => {
|
|
const { handleListModels } = await import("./routes/models/list");
|
|
return handleListModels(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
POST: withErrorHandler(
|
|
async (req) => {
|
|
const { handleCreateModel } = await import("./routes/models/create");
|
|
return handleCreateModel(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/models/:id": {
|
|
DELETE: withErrorHandler(
|
|
async (req) => {
|
|
const { handleDeleteModel } = await import("./routes/models/delete");
|
|
return handleDeleteModel(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
GET: withErrorHandler(
|
|
async (req) => {
|
|
const { handleGetModel } = await import("./routes/models/get");
|
|
return handleGetModel(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
PATCH: withErrorHandler(
|
|
async (req) => {
|
|
const { handleUpdateModel } = await import("./routes/models/update");
|
|
return handleUpdateModel(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/models/test": {
|
|
POST: withErrorHandler(
|
|
async (req) => {
|
|
const { handleTestModelConfig } = await import("./routes/models/test");
|
|
return handleTestModelConfig(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/projects": {
|
|
GET: withErrorHandler(
|
|
async (req) => {
|
|
const { handleListProjects } = await import("./routes/projects/list");
|
|
return handleListProjects(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
POST: withErrorHandler(
|
|
async (req) => {
|
|
const { handleCreateProject } = await import("./routes/projects/create");
|
|
return handleCreateProject(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/projects/:id": {
|
|
DELETE: withErrorHandler(
|
|
async (req) => {
|
|
const { handleDeleteProject } = await import("./routes/projects/delete");
|
|
return handleDeleteProject(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
GET: withErrorHandler(
|
|
async (req) => {
|
|
const { handleGetProject } = await import("./routes/projects/get");
|
|
return handleGetProject(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
PATCH: withErrorHandler(
|
|
async (req) => {
|
|
const { handleUpdateProject } = await import("./routes/projects/update");
|
|
return handleUpdateProject(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/projects/:id/archive": {
|
|
POST: withErrorHandler(
|
|
async (req) => {
|
|
const { handleArchiveProject } = await import("./routes/projects/archive");
|
|
return handleArchiveProject(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/projects/:id/chat": {
|
|
POST: withErrorHandler(
|
|
async (req) => {
|
|
const { handleSendChat } = await import("./routes/chat/send");
|
|
return handleSendChat(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/projects/:id/conversations": {
|
|
GET: withErrorHandler(
|
|
async (req) => {
|
|
const { handleListConversations } = await import("./routes/chat/list");
|
|
return handleListConversations(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
POST: withErrorHandler(
|
|
async (req) => {
|
|
const { handleCreateConversation } = await import("./routes/chat/create");
|
|
return handleCreateConversation(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/projects/:id/conversations/:cid": {
|
|
DELETE: withErrorHandler(
|
|
async (req) => {
|
|
const { handleDeleteConversation } = await import("./routes/chat/delete");
|
|
return handleDeleteConversation(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
GET: withErrorHandler(
|
|
async (req) => {
|
|
const { handleGetConversation } = await import("./routes/chat/get");
|
|
return handleGetConversation(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
PATCH: withErrorHandler(
|
|
async (req) => {
|
|
const { handleUpdateConversation } = await import("./routes/chat/update");
|
|
return handleUpdateConversation(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/projects/:id/conversations/:cid/messages": {
|
|
GET: withErrorHandler(
|
|
async (req) => {
|
|
const { handleListMessages } = await import("./routes/chat/messages");
|
|
return handleListMessages(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/projects/:id/materials": {
|
|
GET: withErrorHandler(
|
|
async (req) => {
|
|
const { handleListMaterials } = await import("./routes/materials/list");
|
|
return handleListMaterials(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
POST: withErrorHandler(
|
|
async (req) => {
|
|
const { handleCreateMaterial } = await import("./routes/materials/create");
|
|
return handleCreateMaterial(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/projects/:id/materials/:mid": {
|
|
DELETE: withErrorHandler(
|
|
async (req) => {
|
|
const { handleDeleteMaterial } = await import("./routes/materials/delete");
|
|
return handleDeleteMaterial(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
GET: withErrorHandler(
|
|
async (req) => {
|
|
const { handleGetMaterial } = await import("./routes/materials/get");
|
|
return handleGetMaterial(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/projects/:id/restore": {
|
|
POST: withErrorHandler(
|
|
async (req) => {
|
|
const { handleRestoreProject } = await import("./routes/projects/restore");
|
|
return handleRestoreProject(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/providers": {
|
|
GET: withErrorHandler(
|
|
async (req) => {
|
|
const { handleListProviders } = await import("./routes/providers/list");
|
|
return handleListProviders(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
POST: withErrorHandler(
|
|
async (req) => {
|
|
const { handleCreateProvider } = await import("./routes/providers/create");
|
|
return handleCreateProvider(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/providers/:id": {
|
|
DELETE: withErrorHandler(
|
|
async (req) => {
|
|
const { handleDeleteProvider } = await import("./routes/providers/delete");
|
|
return handleDeleteProvider(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
GET: withErrorHandler(
|
|
async (req) => {
|
|
const { handleGetProvider } = await import("./routes/providers/get");
|
|
return handleGetProvider(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
PATCH: withErrorHandler(
|
|
async (req) => {
|
|
const { handleUpdateProvider } = await import("./routes/providers/update");
|
|
return handleUpdateProvider(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/providers/options": {
|
|
GET: withErrorHandler(
|
|
async () => {
|
|
const { handleListProviderOptions } = await import("./routes/providers/options");
|
|
return handleListProviderOptions(db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
"/api/providers/test": {
|
|
POST: withErrorHandler(
|
|
async (req) => {
|
|
const { handleTestProviderConfig } = await import("./routes/providers/test");
|
|
return handleTestProviderConfig(req, db, mode, logger);
|
|
},
|
|
mode,
|
|
logger,
|
|
),
|
|
},
|
|
},
|
|
});
|
|
|
|
logger.info({ host: config.host, port: config.port, url: server.url.toString() }, "服务启动");
|
|
|
|
return server;
|
|
}
|