refactor: 统一管理页面布局 — FilterToolbar + usePageSearchParams + parseListParams
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import type Database from "bun:sqlite";
|
||||
|
||||
import { desc, eq, like, or, sql } from "drizzle-orm";
|
||||
import { asc, desc, eq, like, or, sql } from "drizzle-orm";
|
||||
|
||||
import type { CreateModelRequest, Model, ModelCapability, UpdateModelRequest } from "../../shared/api";
|
||||
import type { CreateModelRequest, Model, ModelCapability, SortOrder, UpdateModelRequest } from "../../shared/api";
|
||||
import type { Logger } from "../logger";
|
||||
|
||||
import { paginateQuery, wrap } from "./connection";
|
||||
@@ -124,7 +124,15 @@ export function getModelWithProvider(
|
||||
|
||||
export function listModels(
|
||||
raw: Database,
|
||||
options: { keyword?: string; page: number; pageSize: number; providerId?: string },
|
||||
options: {
|
||||
capabilities?: string;
|
||||
keyword?: string;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
providerId?: string;
|
||||
sortBy?: string;
|
||||
sortOrder?: SortOrder;
|
||||
},
|
||||
): { items: Model[]; page: number; pageSize: number; total: number } {
|
||||
const conditions = [];
|
||||
|
||||
@@ -137,10 +145,16 @@ export function listModels(
|
||||
conditions.push(or(like(models.name, pattern), like(models.modelId, pattern))!);
|
||||
}
|
||||
|
||||
if (options.capabilities) {
|
||||
conditions.push(like(models.capabilities, `%"${options.capabilities}"%`));
|
||||
}
|
||||
|
||||
const orderByFn = buildModelOrderBy(options.sortBy, options.sortOrder);
|
||||
|
||||
return paginateQuery(raw, models, {
|
||||
conditions,
|
||||
mapRow: toModel,
|
||||
orderBy: () => desc(models.createdAt),
|
||||
orderBy: orderByFn,
|
||||
page: options.page,
|
||||
pageSize: options.pageSize,
|
||||
});
|
||||
@@ -212,6 +226,17 @@ export function updateModel(
|
||||
return { model: toModel(updated!) };
|
||||
}
|
||||
|
||||
function buildModelOrderBy(
|
||||
sortBy: string | undefined,
|
||||
sortOrder: SortOrder | undefined,
|
||||
): ((table: typeof models) => ReturnType<typeof desc>) | undefined {
|
||||
if (!sortBy) return (t) => desc(t.createdAt);
|
||||
|
||||
return sortOrder === "asc"
|
||||
? (t) => asc(t[sortBy as keyof typeof t] as Parameters<typeof asc>[0])
|
||||
: (t) => desc(t[sortBy as keyof typeof t] as Parameters<typeof desc>[0]);
|
||||
}
|
||||
|
||||
function toModel(row: typeof models.$inferSelect): Model {
|
||||
return {
|
||||
capabilities: JSON.parse(row.capabilities) as ModelCapability[],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type Database from "bun:sqlite";
|
||||
|
||||
import { desc, eq, like, or } from "drizzle-orm";
|
||||
import { asc, desc, eq, like, or } from "drizzle-orm";
|
||||
|
||||
import type { CreateProjectRequest, Project, ProjectStatus, UpdateProjectRequest } from "../../shared/api";
|
||||
import type { CreateProjectRequest, Project, ProjectStatus, SortOrder, UpdateProjectRequest } from "../../shared/api";
|
||||
import type { Logger } from "../logger";
|
||||
|
||||
import { paginateQuery, wrap } from "./connection";
|
||||
@@ -88,7 +88,14 @@ export function getProject(raw: Database, id: string): { error: string; status:
|
||||
|
||||
export function listProjects(
|
||||
raw: Database,
|
||||
options: { keyword?: string; page: number; pageSize: number; status?: ProjectStatus },
|
||||
options: {
|
||||
keyword?: string;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
sortBy?: string;
|
||||
sortOrder?: SortOrder;
|
||||
status?: ProjectStatus;
|
||||
},
|
||||
): { items: Project[]; page: number; pageSize: number; total: number } {
|
||||
const conditions = [];
|
||||
|
||||
@@ -101,10 +108,12 @@ export function listProjects(
|
||||
conditions.push(or(like(projects.name, pattern), like(projects.description, pattern))!);
|
||||
}
|
||||
|
||||
const orderByFn = buildProjectOrderBy(options.sortBy, options.sortOrder);
|
||||
|
||||
return paginateQuery(raw, projects, {
|
||||
conditions,
|
||||
mapRow: toProject,
|
||||
orderBy: () => desc(projects.createdAt),
|
||||
orderBy: orderByFn,
|
||||
page: options.page,
|
||||
pageSize: options.pageSize,
|
||||
});
|
||||
@@ -174,6 +183,17 @@ export function updateProject(
|
||||
return { project: toProject(updated!) };
|
||||
}
|
||||
|
||||
function buildProjectOrderBy(
|
||||
sortBy: string | undefined,
|
||||
sortOrder: SortOrder | undefined,
|
||||
): ((table: typeof projects) => ReturnType<typeof desc>) | undefined {
|
||||
if (!sortBy) return (t) => desc(t.createdAt);
|
||||
|
||||
return sortOrder === "asc"
|
||||
? (t) => asc(t[sortBy as keyof typeof t] as Parameters<typeof asc>[0])
|
||||
: (t) => desc(t[sortBy as keyof typeof t] as Parameters<typeof desc>[0]);
|
||||
}
|
||||
|
||||
function toProject(row: typeof projects.$inferSelect): Project {
|
||||
return {
|
||||
archivedAt: row.archivedAt,
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import type Database from "bun:sqlite";
|
||||
|
||||
import { desc, eq, like } from "drizzle-orm";
|
||||
import { asc, desc, eq, like } from "drizzle-orm";
|
||||
|
||||
import type { CreateProviderRequest, Provider, ProviderOption, UpdateProviderRequest } from "../../shared/api";
|
||||
import type {
|
||||
CreateProviderRequest,
|
||||
Provider,
|
||||
ProviderOption,
|
||||
SortOrder,
|
||||
UpdateProviderRequest,
|
||||
} from "../../shared/api";
|
||||
import type { Logger } from "../logger";
|
||||
|
||||
import { paginateQuery, wrap } from "./connection";
|
||||
@@ -85,7 +91,7 @@ export function listProviderOptions(raw: Database): ProviderOption[] {
|
||||
|
||||
export function listProviders(
|
||||
raw: Database,
|
||||
options: { keyword?: string; page: number; pageSize: number },
|
||||
options: { keyword?: string; page: number; pageSize: number; sortBy?: string; sortOrder?: SortOrder; type?: string },
|
||||
): { items: Provider[]; page: number; pageSize: number; total: number } {
|
||||
const conditions = [];
|
||||
|
||||
@@ -94,10 +100,16 @@ export function listProviders(
|
||||
conditions.push(like(providers.name, pattern));
|
||||
}
|
||||
|
||||
if (options.type) {
|
||||
conditions.push(eq(providers.type, options.type as Provider["type"]));
|
||||
}
|
||||
|
||||
const orderByFn = buildProviderOrderBy(options.sortBy, options.sortOrder);
|
||||
|
||||
return paginateQuery(raw, providers, {
|
||||
conditions,
|
||||
mapRow: toProvider,
|
||||
orderBy: () => desc(providers.createdAt),
|
||||
orderBy: orderByFn,
|
||||
page: options.page,
|
||||
pageSize: options.pageSize,
|
||||
});
|
||||
@@ -158,6 +170,17 @@ export function updateProvider(
|
||||
return { provider: toProvider(updated!) };
|
||||
}
|
||||
|
||||
function buildProviderOrderBy(
|
||||
sortBy: string | undefined,
|
||||
sortOrder: SortOrder | undefined,
|
||||
): ((table: typeof providers) => ReturnType<typeof desc>) | undefined {
|
||||
if (!sortBy) return (t) => desc(t.createdAt);
|
||||
|
||||
return sortOrder === "asc"
|
||||
? (t) => asc(t[sortBy as keyof typeof t] as Parameters<typeof asc>[0])
|
||||
: (t) => desc(t[sortBy as keyof typeof t] as Parameters<typeof desc>[0]);
|
||||
}
|
||||
|
||||
function toProvider(row: typeof providers.$inferSelect): Provider {
|
||||
return {
|
||||
apiKey: row.apiKey,
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export type { ParsedListParams } from "./list-params";
|
||||
export { parseListParams } from "./list-params";
|
||||
export { createApiError, createHeaders, createMetaResponse, formatDuration, jsonResponse } from "./response";
|
||||
export { parseIdFromUrl } from "./url";
|
||||
|
||||
59
src/server/helpers/list-params.ts
Normal file
59
src/server/helpers/list-params.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { RuntimeMode, SortOrder } from "../../shared/api";
|
||||
|
||||
import { createApiError, jsonResponse } from "./index";
|
||||
|
||||
export interface ParsedListParams {
|
||||
keyword?: string;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
sortBy?: string;
|
||||
sortOrder?: SortOrder;
|
||||
}
|
||||
|
||||
export function parseListParams(
|
||||
url: URL,
|
||||
mode: RuntimeMode,
|
||||
options?: { allowedSortBy?: string[] },
|
||||
): ParsedListParams | Response {
|
||||
const pageParam = url.searchParams.get("page");
|
||||
const pageSizeParam = url.searchParams.get("pageSize");
|
||||
|
||||
let page = 1;
|
||||
let pageSize = 20;
|
||||
|
||||
if (pageParam !== null) {
|
||||
page = Number(pageParam);
|
||||
if (!Number.isInteger(page) || page <= 0) {
|
||||
return jsonResponse(createApiError("无效的 page 参数", 400), { mode, status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
if (pageSizeParam !== null) {
|
||||
pageSize = Number(pageSizeParam);
|
||||
if (!Number.isInteger(pageSize) || pageSize <= 0) {
|
||||
return jsonResponse(createApiError("无效的 pageSize 参数", 400), { mode, status: 400 });
|
||||
}
|
||||
if (pageSize > 200) {
|
||||
return jsonResponse(createApiError("pageSize 不能超过 200", 400), { mode, status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
const keyword = url.searchParams.get("keyword") ?? undefined;
|
||||
|
||||
const sortBy = url.searchParams.get("sortBy") ?? undefined;
|
||||
const sortOrderParam = url.searchParams.get("sortOrder") ?? undefined;
|
||||
|
||||
if (sortBy && options?.allowedSortBy && !options.allowedSortBy.includes(sortBy)) {
|
||||
return jsonResponse(createApiError("无效的 sortBy 参数", 400), { mode, status: 400 });
|
||||
}
|
||||
|
||||
let sortOrder: SortOrder | undefined;
|
||||
if (sortOrderParam) {
|
||||
if (sortOrderParam !== "asc" && sortOrderParam !== "desc") {
|
||||
return jsonResponse(createApiError("无效的 sortOrder 参数", 400), { mode, status: 400 });
|
||||
}
|
||||
sortOrder = sortOrderParam;
|
||||
}
|
||||
|
||||
return { keyword, page, pageSize, sortBy, sortOrder };
|
||||
}
|
||||
@@ -4,24 +4,26 @@ import type { RuntimeMode } from "../../../shared/api";
|
||||
import type { Logger } from "../../logger";
|
||||
|
||||
import { listModels } from "../../db/models";
|
||||
import { jsonResponse } from "../../helpers";
|
||||
import { validatePagination } from "../../middleware";
|
||||
import { jsonResponse, parseListParams } from "../../helpers";
|
||||
|
||||
const ALLOWED_SORT_BY = ["createdAt", "name"];
|
||||
|
||||
export function handleListModels(req: Request, db: Database, mode: RuntimeMode, _logger: Logger): Response {
|
||||
const url = new URL(req.url);
|
||||
const pageParam = url.searchParams.get("page");
|
||||
const pageSizeParam = url.searchParams.get("pageSize");
|
||||
const keyword = url.searchParams.get("keyword");
|
||||
const providerId = url.searchParams.get("providerId");
|
||||
const capabilities = url.searchParams.get("capabilities");
|
||||
|
||||
const pagination = validatePagination(pageParam, pageSizeParam, mode);
|
||||
if (pagination instanceof Response) return pagination;
|
||||
const parsed = parseListParams(url, mode, { allowedSortBy: ALLOWED_SORT_BY });
|
||||
if (parsed instanceof Response) return parsed;
|
||||
|
||||
const result = listModels(db, {
|
||||
keyword: keyword ?? undefined,
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
capabilities: capabilities ?? undefined,
|
||||
keyword: parsed.keyword,
|
||||
page: parsed.page,
|
||||
pageSize: parsed.pageSize,
|
||||
providerId: providerId ?? undefined,
|
||||
sortBy: parsed.sortBy,
|
||||
sortOrder: parsed.sortOrder,
|
||||
});
|
||||
|
||||
return jsonResponse(result, { mode });
|
||||
|
||||
@@ -4,27 +4,27 @@ import type { RuntimeMode } from "../../../shared/api";
|
||||
import type { Logger } from "../../logger";
|
||||
|
||||
import { listProjects } from "../../db/projects";
|
||||
import { createApiError, jsonResponse } from "../../helpers";
|
||||
import { validatePagination } from "../../middleware";
|
||||
import { createApiError, jsonResponse, parseListParams } from "../../helpers";
|
||||
|
||||
const ALLOWED_SORT_BY = ["createdAt", "name", "updatedAt"];
|
||||
|
||||
export function handleListProjects(req: Request, db: Database, mode: RuntimeMode, _logger: Logger): Response {
|
||||
const url = new URL(req.url);
|
||||
const pageParam = url.searchParams.get("page");
|
||||
const pageSizeParam = url.searchParams.get("pageSize");
|
||||
const keyword = url.searchParams.get("keyword");
|
||||
const statusParam = url.searchParams.get("status");
|
||||
|
||||
const pagination = validatePagination(pageParam, pageSizeParam, mode);
|
||||
if (pagination instanceof Response) return pagination;
|
||||
|
||||
if (statusParam && statusParam !== "active" && statusParam !== "archived") {
|
||||
return jsonResponse(createApiError("Invalid status parameter", 400), { mode, status: 400 });
|
||||
}
|
||||
|
||||
const parsed = parseListParams(url, mode, { allowedSortBy: ALLOWED_SORT_BY });
|
||||
if (parsed instanceof Response) return parsed;
|
||||
|
||||
const result = listProjects(db, {
|
||||
keyword: keyword ?? undefined,
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
keyword: parsed.keyword,
|
||||
page: parsed.page,
|
||||
pageSize: parsed.pageSize,
|
||||
sortBy: parsed.sortBy,
|
||||
sortOrder: parsed.sortOrder,
|
||||
status: (statusParam as "active" | "archived") ?? undefined,
|
||||
});
|
||||
|
||||
|
||||
@@ -4,22 +4,24 @@ import type { RuntimeMode } from "../../../shared/api";
|
||||
import type { Logger } from "../../logger";
|
||||
|
||||
import { listProviders } from "../../db/providers";
|
||||
import { jsonResponse } from "../../helpers";
|
||||
import { validatePagination } from "../../middleware";
|
||||
import { jsonResponse, parseListParams } from "../../helpers";
|
||||
|
||||
const ALLOWED_SORT_BY = ["createdAt", "name"];
|
||||
|
||||
export function handleListProviders(req: Request, db: Database, mode: RuntimeMode, _logger: Logger): Response {
|
||||
const url = new URL(req.url);
|
||||
const pageParam = url.searchParams.get("page");
|
||||
const pageSizeParam = url.searchParams.get("pageSize");
|
||||
const keyword = url.searchParams.get("keyword");
|
||||
const typeParam = url.searchParams.get("type");
|
||||
|
||||
const pagination = validatePagination(pageParam, pageSizeParam, mode);
|
||||
if (pagination instanceof Response) return pagination;
|
||||
const parsed = parseListParams(url, mode, { allowedSortBy: ALLOWED_SORT_BY });
|
||||
if (parsed instanceof Response) return parsed;
|
||||
|
||||
const result = listProviders(db, {
|
||||
keyword: keyword ?? undefined,
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
keyword: parsed.keyword,
|
||||
page: parsed.page,
|
||||
pageSize: parsed.pageSize,
|
||||
sortBy: parsed.sortBy,
|
||||
sortOrder: parsed.sortOrder,
|
||||
type: typeParam ?? undefined,
|
||||
});
|
||||
|
||||
return jsonResponse(result, { mode });
|
||||
|
||||
Reference in New Issue
Block a user