- 创建 pnpm monorepo 结构 (pnpm-workspace.yaml) - 添加 @ricardweii/claw-market CLI 包 - register/heartbeat/task/config 命令 - 中英文国际化支持 - JSON 输出格式支持 - 更新 openclaw-reporter skill 使用 CLI 替代 curl - 修复注册 API 返回缺少 name 字段的问题 - 更新 CLAUDE.md 文档说明 monorepo 结构
91 lines
2.4 KiB
TypeScript
91 lines
2.4 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { nanoid } from "nanoid";
|
|
import { db } from "@/lib/db";
|
|
import { claws } from "@/lib/db/schema";
|
|
import {
|
|
setClawOnline,
|
|
updateActiveClaws,
|
|
incrementGlobalStat,
|
|
incrementRegionCount,
|
|
publishEvent,
|
|
} from "@/lib/redis";
|
|
import { generateApiKey } from "@/lib/auth/api-key";
|
|
import { getGeoLocation } from "@/lib/geo/ip-location";
|
|
import { registerSchema } from "@/lib/validators/schemas";
|
|
|
|
function getClientIp(req: NextRequest): string {
|
|
const forwarded = req.headers.get("x-forwarded-for");
|
|
if (forwarded) return forwarded.split(",")[0].trim();
|
|
const realIp = req.headers.get("x-real-ip");
|
|
if (realIp) return realIp.trim();
|
|
return "127.0.0.1";
|
|
}
|
|
|
|
export async function POST(req: NextRequest) {
|
|
try {
|
|
const body = await req.json();
|
|
const parsed = registerSchema.safeParse(body);
|
|
if (!parsed.success) {
|
|
return NextResponse.json(
|
|
{ error: "Validation failed", details: parsed.error.flatten() },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const { name, model, platform } = parsed.data;
|
|
const clawId = nanoid(21);
|
|
const apiKey = generateApiKey();
|
|
const clientIp = getClientIp(req);
|
|
const geo = await getGeoLocation(clientIp);
|
|
const now = new Date();
|
|
|
|
await db.insert(claws).values({
|
|
id: clawId,
|
|
apiKey,
|
|
name,
|
|
model: model ?? null,
|
|
platform: platform ?? null,
|
|
ip: clientIp,
|
|
latitude: geo ? String(geo.latitude) : null,
|
|
longitude: geo ? String(geo.longitude) : null,
|
|
city: geo?.city ?? null,
|
|
country: geo?.country ?? null,
|
|
countryCode: geo?.countryCode ?? null,
|
|
region: geo?.region ?? null,
|
|
lastHeartbeat: now,
|
|
totalTasks: 0,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
});
|
|
|
|
await setClawOnline(clawId, clientIp);
|
|
await updateActiveClaws(clawId);
|
|
await incrementGlobalStat("total_claws");
|
|
|
|
if (geo?.region) {
|
|
await incrementRegionCount(geo.region);
|
|
}
|
|
|
|
await publishEvent({
|
|
type: "registered",
|
|
clawId,
|
|
clawName: name,
|
|
city: geo?.city ?? null,
|
|
country: geo?.country ?? null,
|
|
});
|
|
|
|
return NextResponse.json({
|
|
clawId,
|
|
apiKey,
|
|
name,
|
|
endpoint: `${process.env.NEXT_PUBLIC_APP_URL}/api/v1`,
|
|
});
|
|
} catch (error) {
|
|
console.error("Register error:", error);
|
|
return NextResponse.json(
|
|
{ error: "Internal server error" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|