feat: establish NekoNest Cloud control and relay

This commit is contained in:
2026-08-12 23:25:43 +08:00
commit f27606b709
222 changed files with 71456 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
import { getCloudViewer } from "@/app/cloud-auth";
import {
cancelAccountDeletion,
DomainError,
getOrCreateAccount,
requestAccountDeletion,
} from "@/db/repository";
import { apiError, readJsonMutation } from "../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error: "authentication_required", message: "请先登录" },
{ status: 401, headers: { "cache-control": "no-store" } },
);
}
const payload = await readJsonMutation<{
action?: string;
requestId?: string;
confirmed?: boolean;
reason?: string;
idempotencyKey?: string;
}>(request);
const account = await getOrCreateAccount(viewer);
if (payload.action === "request") {
const deletionRequest = await requestAccountDeletion({
accountId: account.id,
actorId: viewer.userId,
confirmed: payload.confirmed === true,
reason: payload.reason ?? "",
idempotencyKey: payload.idempotencyKey ?? "",
});
return Response.json(
{ deletionRequest },
{ status: 201, headers: { "cache-control": "no-store" } },
);
}
if (payload.action === "cancel") {
const deletionRequest = await cancelAccountDeletion({
accountId: account.id,
actorId: viewer.userId,
requestId: payload.requestId ?? "",
});
return Response.json(
{ deletionRequest },
{ headers: { "cache-control": "no-store" } },
);
}
throw new DomainError(
"invalid_deletion_action",
"请选择有效的注销申请操作",
);
} catch (error) {
const response = apiError(error);
response.headers.set("cache-control", "no-store");
return response;
}
}
+33
View File
@@ -0,0 +1,33 @@
import { getCloudViewer } from "@/app/cloud-auth";
import {
getAccountControlPlaneExport,
getOrCreateAccount,
} from "@/db/repository";
import { apiError } from "../../respond";
export async function GET() {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error: "authentication_required", message: "请先登录" },
{ status: 401, headers: { "cache-control": "no-store" } },
);
}
const account = await getOrCreateAccount(viewer);
const exportData = await getAccountControlPlaneExport(account);
const date = exportData.exported_at.slice(0, 10);
return new Response(`${JSON.stringify(exportData, null, 2)}\n`, {
headers: {
"cache-control": "no-store",
"content-disposition": `attachment; filename="nekonest-cloud-${date}.json"`,
"content-type": "application/json; charset=utf-8",
"x-content-type-options": "nosniff",
},
});
} catch (error) {
const response = apiError(error);
response.headers.set("cache-control", "no-store");
return response;
}
}
+43
View File
@@ -0,0 +1,43 @@
import { getCloudViewer } from "@/app/cloud-auth";
import { resolveBetaAccessRequest } from "@/db/repository";
import { apiError, readJsonMutation } from "../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error: "authentication_required", message: "请先登录" },
{ status: 401 },
);
}
if (!viewer.isAdmin) {
return Response.json(
{ error: "forbidden", message: "没有商业后台权限" },
{ status: 403 },
);
}
const payload = await readJsonMutation<{
requestId?: string;
action?: "approve" | "decline";
capacitySlots?: number;
endsAt?: string;
response?: string;
reason?: string;
idempotencyKey?: string;
}>(request);
const result = await resolveBetaAccessRequest({
actorId: viewer.userId,
requestId: payload.requestId ?? "",
action: payload.action ?? "decline",
capacitySlots: payload.capacitySlots ?? 0,
endsAt: payload.endsAt ?? "",
response: payload.response ?? "",
reason: payload.reason ?? "",
idempotencyKey: payload.idempotencyKey ?? "",
});
return Response.json(result);
} catch (error) {
return apiError(error);
}
}
+14
View File
@@ -0,0 +1,14 @@
import { getCloudViewer } from "@/app/cloud-auth";
import { setPublicBeta } from "@/db/repository";
import { apiError, readJsonMutation } from "../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) return Response.json({ error: "authentication_required", message: "请先登录" }, { status: 401 });
if (!viewer.isAdmin) return Response.json({ error: "forbidden", message: "没有商业后台权限" }, { status: 403 });
const payload = await readJsonMutation<{ enabled?: boolean; capacitySlots?: number | null; reason?: string; idempotencyKey?: string }>(request);
const beta = await setPublicBeta({ actorId: viewer.userId, enabled: payload.enabled ?? false, capacitySlots: payload.capacitySlots ?? null, reason: payload.reason ?? "", idempotencyKey: payload.idempotencyKey ?? "" });
return Response.json({ beta });
} catch (error) { return apiError(error); }
}
+18
View File
@@ -0,0 +1,18 @@
import { getCloudViewer } from "@/app/cloud-auth";
import { createExemption, revokeExemption } from "@/db/repository";
import { apiError, readJsonMutation } from "../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) return Response.json({ error: "authentication_required", message: "请先登录" }, { status: 401 });
if (!viewer.isAdmin) return Response.json({ error: "forbidden", message: "没有商业后台权限" }, { status: 403 });
const payload = await readJsonMutation<{ action?: string; grantId?: string; accountId?: string; capacitySlots?: number | null; endsAt?: string; reason?: string; idempotencyKey?: string }>(request);
if (payload.action === "revoke") {
const grant = await revokeExemption({ actorId: viewer.userId, grantId: payload.grantId ?? "", reason: payload.reason ?? "", idempotencyKey: payload.idempotencyKey ?? "" });
return Response.json({ grant });
}
const grant = await createExemption({ actorId: viewer.userId, accountId: payload.accountId ?? "", capacitySlots: payload.capacitySlots ?? null, endsAt: payload.endsAt ?? "", reason: payload.reason ?? "", idempotencyKey: payload.idempotencyKey ?? "" });
return Response.json({ grant }, { status: 201 });
} catch (error) { return apiError(error); }
}
+37
View File
@@ -0,0 +1,37 @@
import { getCloudViewer } from "@/app/cloud-auth";
import { resolveFeedback } from "@/db/repository";
import { apiError, readJsonMutation } from "../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error: "authentication_required", message: "请先登录" },
{ status: 401 },
);
}
if (!viewer.isAdmin) {
return Response.json(
{ error: "forbidden", message: "没有公测后台权限" },
{ status: 403 },
);
}
const payload = await readJsonMutation<{
feedbackId?: string;
response?: string;
reason?: string;
idempotencyKey?: string;
}>(request);
const feedback = await resolveFeedback({
feedbackId: payload.feedbackId ?? "",
actorId: viewer.userId,
response: payload.response ?? "",
reason: payload.reason ?? "",
idempotencyKey: payload.idempotencyKey ?? "",
});
return Response.json({ feedback });
} catch (error) {
return apiError(error);
}
}
+61
View File
@@ -0,0 +1,61 @@
import { getCloudViewer } from "@/app/cloud-auth";
import {
createServiceIncident,
resolveServiceIncident,
} from "@/db/repository";
import { apiError, readJsonMutation } from "../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error: "authentication_required", message: "请先登录" },
{ status: 401 },
);
}
if (!viewer.isAdmin) {
return Response.json(
{ error: "forbidden", message: "没有公测后台权限" },
{ status: 403 },
);
}
const payload = await readJsonMutation<{
action?: string;
incidentId?: string;
severity?: string;
title?: string;
message?: string;
resolution?: string;
reason?: string;
idempotencyKey?: string;
}>(request);
if (payload.action === "create") {
const incident = await createServiceIncident({
actorId: viewer.userId,
severity: payload.severity ?? "",
title: payload.title ?? "",
message: payload.message ?? "",
reason: payload.reason ?? "",
idempotencyKey: payload.idempotencyKey ?? "",
});
return Response.json({ incident }, { status: 201 });
}
if (payload.action === "resolve") {
const incident = await resolveServiceIncident({
incidentId: payload.incidentId ?? "",
actorId: viewer.userId,
resolution: payload.resolution ?? "",
reason: payload.reason ?? "",
});
return Response.json({ incident });
}
return Response.json(
{ error: "invalid_incident_action", message: "请选择有效的故障公告操作" },
{ status: 400 },
);
} catch (error) {
return apiError(error);
}
}
+14
View File
@@ -0,0 +1,14 @@
import { getCloudViewer } from "@/app/cloud-auth";
import { updateLaunchGate, type LaunchGateRecord } from "@/db/repository";
import { apiError, readJsonMutation } from "../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) return Response.json({ error: "authentication_required", message: "请先登录" }, { status: 401 });
if (!viewer.isAdmin) return Response.json({ error: "forbidden", message: "没有商业后台权限" }, { status: 403 });
const payload = await readJsonMutation<{ key?: string; status?: LaunchGateRecord["status"]; owner?: string; evidenceUrl?: string; notes?: string; reason?: string; idempotencyKey?: string }>(request);
const gate = await updateLaunchGate({ actorId: viewer.userId, key: payload.key ?? "", status: payload.status ?? "blocked", owner: payload.owner ?? "", evidenceUrl: payload.evidenceUrl ?? "", notes: payload.notes ?? "", reason: payload.reason ?? "", idempotencyKey: payload.idempotencyKey ?? "" });
return Response.json({ gate });
} catch (error) { return apiError(error); }
}
+13
View File
@@ -0,0 +1,13 @@
import { getCloudViewer } from "@/app/cloud-auth";
import { DomainError } from "@/db/repository";
import { apiError, readJsonMutation } from "../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) return Response.json({ error: "authentication_required", message: "请先登录" }, { status: 401 });
if (!viewer.isAdmin) return Response.json({ error: "forbidden", message: "没有商业后台权限" }, { status: 403 });
await readJsonMutation<{ period?: "month" | "year"; amountMinor?: number; reason?: string; idempotencyKey?: string }>(request);
throw new DomainError("paid_features_deferred", "免费公测阶段不发布价格版本", 409);
} catch (error) { return apiError(error); }
}
+40
View File
@@ -0,0 +1,40 @@
import { getCloudViewer } from "@/app/cloud-auth";
import { beginRelayMigration } from "@/db/relay-migrations";
import { apiError, readJsonMutation } from "../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error_code: "authentication_required", message: "请先登录", retryable: false },
{ status: 401 },
);
}
if (!viewer.isAdmin) {
return Response.json(
{ error_code: "forbidden", message: "没有 Relay 迁移权限", retryable: false },
{ status: 403 },
);
}
const payload = await readJsonMutation<{
tenant_id?: string;
target_node_id?: string;
reason?: string;
idempotency_key?: string;
}>(request);
const migration = await beginRelayMigration({
tenantId: payload.tenant_id ?? "",
targetNodeId: payload.target_node_id ?? "",
actorId: viewer.userId,
reason: payload.reason ?? "",
idempotencyKey: payload.idempotency_key ?? "",
});
return Response.json(
{ migration_id: migration.id, state: migration.state, started_at: migration.started_at },
{ status: 202, headers: { "cache-control": "no-store" } },
);
} catch (error) {
return apiError(error);
}
}
+38
View File
@@ -0,0 +1,38 @@
import { getCloudViewer } from "@/app/cloud-auth";
import { beginRelayPurge } from "@/db/relay-purges";
import { apiError, readJsonMutation } from "../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error_code: "authentication_required", message: "请先登录", retryable: false },
{ status: 401 },
);
}
if (!viewer.isAdmin) {
return Response.json(
{ error_code: "forbidden", message: "没有永久删除租户数据的权限", retryable: false },
{ status: 403 },
);
}
const payload = await readJsonMutation<{
deletion_request_id?: string;
reason?: string;
confirmation?: string;
}>(request);
const purge = await beginRelayPurge({
deletionRequestId: payload.deletion_request_id ?? "",
actorId: viewer.userId,
reason: payload.reason ?? "",
confirmation: payload.confirmation ?? "",
});
return Response.json(
{ purge_id: purge.id, state: purge.state, started_at: purge.started_at },
{ status: 202, headers: { "cache-control": "no-store" } },
);
} catch (error) {
return apiError(error);
}
}
+34
View File
@@ -0,0 +1,34 @@
import { getCloudViewer } from "@/app/cloud-auth";
import { runRetentionMaintenance } from "@/db/repository";
import { apiError, readJsonMutation } from "../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error: "authentication_required", message: "请先登录" },
{ status: 401 },
);
}
if (!viewer.isAdmin) {
return Response.json(
{ error: "forbidden", message: "没有公测后台权限" },
{ status: 403 },
);
}
const payload = await readJsonMutation<{
confirmed?: boolean;
reason?: string;
}>(request);
const retention = await runRetentionMaintenance({
actorId: viewer.userId,
confirmed: payload.confirmed === true,
reason: payload.reason ?? "",
});
return Response.json({ retention });
} catch (error) {
return apiError(error);
}
}
+54
View File
@@ -0,0 +1,54 @@
import { getCloudViewer } from "@/app/cloud-auth";
import {
cancelBetaAccessRequest,
createBetaAccessRequest,
getOrCreateAccount,
} from "@/db/repository";
import { apiError, readJsonMutation } from "../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error: "authentication_required", message: "请先登录" },
{ status: 401 },
);
}
const payload = await readJsonMutation<{
action?: string;
requestId?: string;
preferredOs?: string;
requestedSlots?: number;
useCase?: string;
idempotencyKey?: string;
}>(request);
const account = await getOrCreateAccount(viewer);
if (payload.action === "cancel") {
const accessRequest = await cancelBetaAccessRequest({
accountId: account.id,
actorId: viewer.userId,
requestId: payload.requestId ?? "",
idempotencyKey: payload.idempotencyKey ?? "",
});
return Response.json({ accessRequest });
}
if (payload.action !== "request") {
return Response.json(
{ error: "invalid_action", message: "请选择提交或撤回闭测申请" },
{ status: 400 },
);
}
const accessRequest = await createBetaAccessRequest({
accountId: account.id,
actorId: viewer.userId,
preferredOs: payload.preferredOs ?? "",
requestedSlots: payload.requestedSlots ?? 0,
useCase: payload.useCase ?? "",
idempotencyKey: payload.idempotencyKey ?? "",
});
return Response.json({ accessRequest }, { status: 201 });
} catch (error) {
return apiError(error);
}
}
+27
View File
@@ -0,0 +1,27 @@
import { getCloudViewer } from "@/app/cloud-auth";
import { DomainError } from "@/db/repository";
import { apiError, readJsonMutation } from "../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error: "authentication_required", message: "请先登录" },
{ status: 401 },
);
}
await readJsonMutation<{
period?: "month" | "year";
quantity?: number;
idempotencyKey?: string;
}>(request);
throw new DomainError(
"paid_features_deferred",
"免费公测阶段不提供报价或订单;未来收费方案确定后会另行通知",
409,
);
} catch (error) {
return apiError(error);
}
}
+31
View File
@@ -0,0 +1,31 @@
import { getCloudViewer } from "@/app/cloud-auth";
import { createFeedback, getOrCreateAccount } from "@/db/repository";
import { apiError, readJsonMutation } from "../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error: "authentication_required", message: "请先登录" },
{ status: 401 },
);
}
const payload = await readJsonMutation<{
category?: string;
message?: string;
idempotencyKey?: string;
}>(request);
const account = await getOrCreateAccount(viewer);
const feedback = await createFeedback({
accountId: account.id,
actorId: viewer.userId,
category: payload.category ?? "",
message: payload.message ?? "",
idempotencyKey: payload.idempotencyKey ?? "",
});
return Response.json({ feedback }, { status: 201 });
} catch (error) {
return apiError(error);
}
}
+33
View File
@@ -0,0 +1,33 @@
import { getCloudViewer } from "@/app/cloud-auth";
import {
cancelPairingRequest,
getOrCreateAccount,
} from "@/db/repository";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error: "authentication_required", message: "请先登录" },
{ status: 401, headers: { "cache-control": "no-store" } },
);
}
const payload = await readJsonMutation<{ pairingId?: string }>(request);
const account = await getOrCreateAccount(viewer);
const result = await cancelPairingRequest({
accountId: account.id,
pairingId: payload.pairingId ?? "",
actorId: viewer.userId,
});
return Response.json(result, {
status: 200,
headers: { "cache-control": "no-store" },
});
} catch (error) {
const response = apiError(error);
response.headers.set("cache-control", "no-store");
return response;
}
}
+64
View File
@@ -0,0 +1,64 @@
import { getCloudViewer } from "@/app/cloud-auth";
import {
createPairingRequest,
getOrCreateAccount,
getOwnedPairingProgress,
} from "@/db/repository";
import { apiError, readJsonMutation } from "../../respond";
export async function GET(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error: "authentication_required", message: "请先登录" },
{ status: 401, headers: { "cache-control": "no-store" } },
);
}
const pairingId = new URL(request.url).searchParams.get("pairing_id") ?? "";
const account = await getOrCreateAccount(viewer);
const pairing = await getOwnedPairingProgress({
accountId: account.id,
pairingId,
});
return Response.json(
{ pairing },
{ status: 200, headers: { "cache-control": "no-store" } },
);
} catch (error) {
const response = apiError(error);
response.headers.set("cache-control", "no-store");
return response;
}
}
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error: "authentication_required", message: "请先登录" },
{ status: 401 },
);
}
const payload = await readJsonMutation<{
requestedName?: string;
os?: "windows" | "linux";
}>(request);
const account = await getOrCreateAccount(viewer);
const pairing = await createPairingRequest({
accountId: account.id,
requestedName: payload.requestedName ?? "",
os: payload.os ?? "windows",
actorId: viewer.userId,
});
return Response.json(
{ pairing },
{ status: 201, headers: { "cache-control": "no-store" } },
);
} catch (error) {
const response = apiError(error);
response.headers.set("cache-control", "no-store");
return response;
}
}
+28
View File
@@ -0,0 +1,28 @@
import { getCloudViewer } from "@/app/cloud-auth";
import { getOrCreateAccount, revokeHost } from "@/db/repository";
import { apiError, readJsonMutation } from "../../respond";
export async function POST(request: Request) {
try {
const viewer = await getCloudViewer();
if (!viewer) {
return Response.json(
{ error: "authentication_required", message: "请先登录" },
{ status: 401 },
);
}
const payload = await readJsonMutation<{ hostId?: string; reason?: string }>(
request,
);
const account = await getOrCreateAccount(viewer);
const result = await revokeHost({
accountId: account.id,
hostId: payload.hostId ?? "",
actorId: viewer.userId,
reason: payload.reason ?? "用户从控制台撤销主机",
});
return Response.json(result);
} catch (error) {
return apiError(error);
}
}
@@ -0,0 +1,25 @@
import {
authenticateRelayNode,
authorizationRevisionDelta,
} from "@/db/relay-control-plane";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{
tenant_id?: string;
after_revision?: number;
}>(request);
return Response.json(
await authorizationRevisionDelta({
principal,
tenantId: payload.tenant_id ?? "",
afterRevision: Number(payload.after_revision ?? -1),
}),
{ headers: { "cache-control": "no-store" } },
);
} catch (error) {
return apiError(error);
}
}
@@ -0,0 +1,22 @@
import {
authenticateRelayNode,
fullAuthorizationSnapshot,
} from "@/db/relay-control-plane";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{
tenant_id?: string;
placement_generation?: number;
}>(request);
return Response.json(await fullAuthorizationSnapshot({
principal,
tenantId: payload.tenant_id ?? "",
placementGeneration: Number(payload.placement_generation ?? -1),
}), { headers: { "cache-control": "no-store" } });
} catch (error) {
return apiError(error);
}
}
@@ -0,0 +1,23 @@
import {
authenticateRelayNode,
authorizeDeviceForRelay,
} from "@/db/relay-control-plane";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{
device_id?: string;
token_hash?: string;
}>(request);
const result = await authorizeDeviceForRelay({
principal,
deviceId: payload.device_id ?? "",
tokenHash: payload.token_hash?.trim().toLowerCase() ?? "",
});
return Response.json(result, { headers: { "cache-control": "no-store" } });
} catch (error) {
return apiError(error);
}
}
@@ -0,0 +1,25 @@
import {
authenticateRelayNode,
authorizePhoneRoute,
} from "@/db/relay-control-plane";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{
route_handle?: string;
phone_token_hash?: string;
}>(request);
return Response.json(
await authorizePhoneRoute({
principal,
routeHandle: payload.route_handle ?? "",
phoneTokenHash: payload.phone_token_hash ?? "",
}),
{ headers: { "cache-control": "no-store" } },
);
} catch (error) {
return apiError(error);
}
}
@@ -0,0 +1,26 @@
import {
authenticateRelayNode,
completePhoneHandoffForRelay,
} from "@/db/relay-control-plane";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{
handoff_id?: string;
phone_id?: string;
phone_token_hash?: string;
route_handle_hash?: string;
}>(request);
return Response.json(await completePhoneHandoffForRelay({
principal,
handoffId: payload.handoff_id ?? "",
phoneId: payload.phone_id ?? "",
phoneTokenHash: payload.phone_token_hash ?? "",
routeHandleHash: payload.route_handle_hash ?? "",
}), { headers: { "cache-control": "no-store" } });
} catch (error) {
return apiError(error);
}
}
@@ -0,0 +1,30 @@
import {
authenticateRelayNode,
consumePhoneHandoffForRelay,
} from "@/db/relay-control-plane";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{
ticket?: string;
pwa_origin?: string;
name?: string;
phone_ed25519_public?: string;
phone_x25519_public?: string;
identity_fingerprint?: string;
}>(request);
return Response.json(await consumePhoneHandoffForRelay({
principal,
ticket: payload.ticket ?? "",
pwaOrigin: payload.pwa_origin ?? "",
name: payload.name ?? "",
phoneEd25519Public: payload.phone_ed25519_public ?? "",
phoneX25519Public: payload.phone_x25519_public ?? "",
identityFingerprint: payload.identity_fingerprint ?? "",
}), { headers: { "cache-control": "no-store" } });
} catch (error) {
return apiError(error);
}
}
+32
View File
@@ -0,0 +1,32 @@
import {
authenticateRelayNode,
heartbeatRelayNode,
} from "@/db/relay-control-plane";
import { relayMigrationAssignments } from "@/db/relay-migrations";
import { relayPurgeAssignments } from "@/db/relay-purges";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{
generation?: number;
capacity_tenants?: number;
}>(request);
const heartbeat = await heartbeatRelayNode({
principal,
generation: Number(payload.generation ?? -1),
capacityTenants: Number(payload.capacity_tenants ?? -1),
});
const [migrations, purges] = await Promise.all([
relayMigrationAssignments(principal),
relayPurgeAssignments(principal),
]);
return Response.json(
{ ...heartbeat, migrations, purges },
{ headers: { "cache-control": "no-store" } },
);
} catch (error) {
return apiError(error);
}
}
@@ -0,0 +1,36 @@
import { authenticateRelayNode } from "@/db/relay-control-plane";
import { advanceRelayMigration } from "@/db/relay-migrations";
import { apiError, readJsonMutation } from "../../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{
migration_id?: string;
action?: "quiesced" | "copied" | "switched" | "finalized" | "failed";
backup_ref?: string;
manifest_sha256?: string;
error_code?: string;
}>(request);
if (!payload.action) {
return Response.json(
{ error_code: "invalid_relay_migration", message: "迁移动作无效", retryable: false },
{ status: 400, headers: { "cache-control": "no-store" } },
);
}
const migration = await advanceRelayMigration({
principal,
migrationId: payload.migration_id ?? "",
action: payload.action,
backupRef: payload.backup_ref,
manifestSha256: payload.manifest_sha256,
errorCode: payload.error_code,
});
return Response.json(
{ migration_id: migration.id, state: migration.state, updated_at: migration.updated_at },
{ headers: { "cache-control": "no-store" } },
);
} catch (error) {
return apiError(error);
}
}
@@ -0,0 +1,34 @@
import { authenticateRelayNode } from "@/db/relay-control-plane";
import { advanceRelayPurge } from "@/db/relay-purges";
import { apiError, readJsonMutation } from "../../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{
purge_id?: string;
action?: "completed" | "failed";
evidence_sha256?: string;
error_code?: string;
}>(request);
if (!payload.action) {
return Response.json(
{ error_code: "invalid_relay_purge", message: "租户删除动作无效", retryable: false },
{ status: 400, headers: { "cache-control": "no-store" } },
);
}
const purge = await advanceRelayPurge({
principal,
purgeId: payload.purge_id ?? "",
action: payload.action,
evidenceSha256: payload.evidence_sha256,
errorCode: payload.error_code,
});
return Response.json(
{ purge_id: purge.id, state: purge.state, updated_at: purge.updated_at },
{ headers: { "cache-control": "no-store" } },
);
} catch (error) {
return apiError(error);
}
}
@@ -0,0 +1,45 @@
import { authenticateRelayNode } from "@/db/relay-control-plane";
import { claimDevice, DomainError } from "@/db/repository";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
await authenticateRelayNode(request);
const payload = await readJsonMutation<{
bootstrap_token?: string;
source_hash?: string;
os?: string;
ed25519_public?: string;
x25519_public?: string;
identity_fingerprint?: string;
transport_mode?: string;
registration_proof?: string;
daemon_version?: string;
registration_retry_key?: string;
}>(request);
const sourceHash = payload.source_hash?.trim().toLowerCase() ?? "";
if (!/^[0-9a-f]{64}$/u.test(sourceHash)) {
throw new DomainError("registration_rate_limited", "注册来源摘要无效", 429, true, 60);
}
const result = await claimDevice({
bootstrapToken: payload.bootstrap_token ?? "",
trustedSourceHash: sourceHash,
os: payload.os ?? "",
ed25519Public: payload.ed25519_public ?? "",
x25519Public: payload.x25519_public ?? "",
identityFingerprint: payload.identity_fingerprint ?? "",
transportMode: payload.transport_mode ?? "",
registrationProof: payload.registration_proof ?? "",
daemonVersion: payload.daemon_version ?? "",
registrationRetryKey: payload.registration_retry_key ?? "",
});
return Response.json(result, {
status: 200,
headers: { "cache-control": "no-store" },
});
} catch (error) {
const response = apiError(error);
response.headers.set("cache-control", "no-store");
return response;
}
}
@@ -0,0 +1,22 @@
import {
authenticateRelayNode,
resolveDeviceRouteForRelay,
} from "@/db/relay-control-plane";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{ device_id?: string; token_hash?: string }>(request);
return Response.json(
await resolveDeviceRouteForRelay({
principal,
deviceId: payload.device_id ?? "",
tokenHash: payload.token_hash ?? "",
}),
{ headers: { "cache-control": "no-store" } },
);
} catch (error) {
return apiError(error);
}
}
@@ -0,0 +1,22 @@
import {
authenticateRelayNode,
resolveHandoffRouteForRelay,
} from "@/db/relay-control-plane";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{ ticket?: string; pwa_origin?: string }>(request);
return Response.json(
await resolveHandoffRouteForRelay({
principal,
ticket: payload.ticket ?? "",
pwaOrigin: payload.pwa_origin ?? "",
}),
{ headers: { "cache-control": "no-store" } },
);
} catch (error) {
return apiError(error);
}
}
@@ -0,0 +1,25 @@
import {
authenticateRelayNode,
resolvePhoneRouteForRelay,
} from "@/db/relay-control-plane";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{
route_handle?: string;
phone_token_hash?: string;
}>(request);
return Response.json(
await resolvePhoneRouteForRelay({
principal,
routeHandle: payload.route_handle ?? "",
phoneTokenHash: payload.phone_token_hash ?? "",
}),
{ headers: { "cache-control": "no-store" } },
);
} catch (error) {
return apiError(error);
}
}
@@ -0,0 +1,25 @@
import {
authenticateRelayNode,
resolveTenantRouteForRelay,
} from "@/db/relay-control-plane";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{
tenant_id?: string;
placement_generation?: number;
}>(request);
return Response.json(
await resolveTenantRouteForRelay({
principal,
tenantId: payload.tenant_id ?? "",
placementGeneration: Number(payload.placement_generation ?? -1),
}),
{ headers: { "cache-control": "no-store" } },
);
} catch (error) {
return apiError(error);
}
}
@@ -0,0 +1,24 @@
import {
authenticateRelayNode,
revokePhoneForRelay,
} from "@/db/relay-control-plane";
import { apiError, readJsonMutation } from "../../../respond";
export async function POST(request: Request) {
try {
const principal = await authenticateRelayNode(request);
const payload = await readJsonMutation<{
tenant_id?: string;
phone_id?: string;
reason?: string;
}>(request);
return Response.json(await revokePhoneForRelay({
principal,
tenantId: payload.tenant_id ?? "",
phoneId: payload.phone_id ?? "",
reason: payload.reason ?? "",
}), { headers: { "cache-control": "no-store" } });
} catch (error) {
return apiError(error);
}
}
+23
View File
@@ -0,0 +1,23 @@
import { env } from "cloudflare:workers";
import { getCloudViewer } from "@/app/cloud-auth";
import { createPhoneHandoffTicket } from "@/db/relay-control-plane";
import { DomainError, getOrCreateAccount } from "@/db/repository";
import { apiError } from "../../respond";
export async function POST() {
try {
const viewer = await getCloudViewer();
if (!viewer) throw new DomainError("authentication_required", "请先登录", 401);
const pwaOrigin = env.NEKONEST_CLOUD_PWA_ORIGIN?.trim() ?? "";
if (!pwaOrigin) {
throw new DomainError("pwa_handoff_unavailable", "Cloud PWA 地址尚未配置", 503, true, 30);
}
const account = await getOrCreateAccount(viewer);
return Response.json(
await createPhoneHandoffTicket({ accountId: account.id, pwaOrigin }),
{ status: 201, headers: { "cache-control": "no-store" } },
);
} catch (error) {
return apiError(error);
}
}
+72
View File
@@ -0,0 +1,72 @@
import { DomainError } from "@/db/repository";
function requireJsonMutation(request: Request): void {
const contentType = request.headers
.get("content-type")
?.split(";", 1)[0]
.trim()
.toLowerCase();
if (contentType !== "application/json") {
throw new DomainError(
"json_required",
"写操作只接受 application/json",
415,
);
}
const contentLength = Number(request.headers.get("content-length") ?? "0");
if (Number.isFinite(contentLength) && contentLength > 32_768) {
throw new DomainError("request_too_large", "请求内容过大", 413);
}
const origin = request.headers.get("origin");
if (origin && origin !== new URL(request.url).origin) {
throw new DomainError("invalid_origin", "拒绝跨站写操作", 403);
}
const fetchSite = request.headers.get("sec-fetch-site");
if (fetchSite && fetchSite !== "same-origin") {
throw new DomainError("invalid_fetch_site", "拒绝跨站写操作", 403);
}
}
export async function readJsonMutation<T>(request: Request): Promise<T> {
requireJsonMutation(request);
const body = await request.text();
if (new TextEncoder().encode(body).byteLength > 32_768) {
throw new DomainError("request_too_large", "请求内容过大", 413);
}
try {
return JSON.parse(body) as T;
} catch {
throw new DomainError("invalid_json", "请求不是有效 JSON", 400);
}
}
export function apiError(error: unknown) {
if (error instanceof DomainError) {
const headers = new Headers({ "cache-control": "no-store" });
if (error.retryAfterSeconds !== undefined) {
headers.set("retry-after", String(error.retryAfterSeconds));
}
return Response.json({
error_code: error.code,
error: error.code,
message: error.message,
retryable: error.retryable,
...(error.retryAfterSeconds === undefined
? {}
: { retry_after_seconds: error.retryAfterSeconds }),
...(error.actionUrl === undefined ? {} : { action_url: error.actionUrl }),
}, { status: error.status, headers });
}
console.error("Unhandled NekoNest Cloud API error", error);
return Response.json(
{
error_code: "internal_error",
error: "internal_error",
message: "服务暂时不可用,请稍后重试",
retryable: true,
},
{ status: 500, headers: { "cache-control": "no-store" } },
);
}
+15
View File
@@ -0,0 +1,15 @@
import { getServiceStatusSnapshot } from "@/db/repository";
import { apiError } from "../respond";
export async function GET() {
try {
const snapshot = await getServiceStatusSnapshot();
return Response.json(snapshot, {
headers: { "cache-control": "no-store" },
});
} catch (error) {
const response = apiError(error);
response.headers.set("cache-control", "no-store");
return response;
}
}