- Add libs/api/admin with admin API endpoints: sync models, workspace credit, billing alert check - Add workspace_alert_config model and alert service - Add Session::no_op() for background tasks without user context - Add admin/ Next.js admin panel (AI models, billing, workspaces, audit) - Start billing alert background task every 30 minutes
97 lines
3.1 KiB
TypeScript
97 lines
3.1 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { query } from "@/lib/db";
|
|
|
|
export const runtime = "nodejs";
|
|
|
|
export async function GET(req: NextRequest) {
|
|
try {
|
|
const { searchParams } = req.nextUrl;
|
|
const page = Math.max(1, parseInt(searchParams.get("page") || "1", 10));
|
|
const pageSize = Math.max(1, parseInt(searchParams.get("pageSize") || "30", 10));
|
|
const projectId = searchParams.get("projectId") || "";
|
|
const search = searchParams.get("search") || "";
|
|
const offset = (page - 1) * pageSize;
|
|
|
|
const conditions: string[] = ["1=1"];
|
|
const params: (string | number)[] = [];
|
|
let paramIdx = 1;
|
|
|
|
if (projectId) {
|
|
conditions.push(`r.project = $${paramIdx++}`);
|
|
params.push(projectId);
|
|
}
|
|
if (search) {
|
|
conditions.push(`r.repo_name ILIKE $${paramIdx++}`);
|
|
params.push(`%${search}%`);
|
|
}
|
|
|
|
const whereCond = conditions.join(" AND ");
|
|
const limitIdx = paramIdx++;
|
|
const offsetIdx = paramIdx++;
|
|
|
|
const [reposResult, countResult] = await Promise.all([
|
|
query<{
|
|
id: string;
|
|
repo_name: string;
|
|
project_id: string;
|
|
project_name: string;
|
|
workspace_name: string;
|
|
default_branch: string;
|
|
is_private: boolean;
|
|
created_by: string;
|
|
created_at: Date;
|
|
ai_code_review_enabled: boolean;
|
|
collaborator_count: string;
|
|
branch_count: string;
|
|
}>(
|
|
`SELECT
|
|
r.id, r.repo_name, r.project as project_id,
|
|
p.name as project_name,
|
|
COALESCE(w.name, '') as workspace_name,
|
|
r.default_branch, r.is_private,
|
|
r.created_by::text as created_by,
|
|
r.created_at::text as created_at,
|
|
r.ai_code_review_enabled,
|
|
(SELECT COUNT(*) FROM repo_collaborator WHERE repo = r.id)::text as collaborator_count,
|
|
(SELECT COUNT(*) FROM repo_branch WHERE repo = r.id)::text as branch_count
|
|
FROM repo r
|
|
JOIN project p ON p.id = r.project
|
|
LEFT JOIN workspace w ON w.id = p.workspace_id
|
|
WHERE ${whereCond}
|
|
ORDER BY r.created_at DESC
|
|
LIMIT $${limitIdx} OFFSET $${offsetIdx}`,
|
|
[...params, pageSize, offset]
|
|
),
|
|
query<{ count: string }>(
|
|
`SELECT COUNT(*)::text as count
|
|
FROM repo r
|
|
JOIN project p ON p.id = r.project
|
|
WHERE ${whereCond}`,
|
|
params
|
|
),
|
|
]);
|
|
|
|
const repos = reposResult.rows.map((r) => ({
|
|
id: r.id,
|
|
repoName: r.repo_name,
|
|
projectId: r.project_id,
|
|
projectName: r.project_name,
|
|
workspaceName: r.workspace_name,
|
|
defaultBranch: r.default_branch,
|
|
isPrivate: r.is_private,
|
|
createdBy: r.created_by,
|
|
createdAt: String(r.created_at),
|
|
aiCodeReviewEnabled: r.ai_code_review_enabled,
|
|
collaboratorCount: parseInt(r.collaborator_count || "0", 10),
|
|
branchCount: parseInt(r.branch_count || "0", 10),
|
|
}));
|
|
|
|
const total = parseInt(countResult.rows[0]?.count || "0", 10);
|
|
|
|
return NextResponse.json({ repos, total, page, pageSize });
|
|
} catch (e) {
|
|
console.error("Repos error:", e);
|
|
return NextResponse.json({ error: "服务器错误" }, { status: 500 });
|
|
}
|
|
}
|