家庭守护圈:多人加入、角色权限、24 小时邀请链接、旧设备绑定自动兼容。
家庭动态:老人/家属发言、三种可见范围,家庭内部内容不会播给老人。 共同照护:家庭备忘录、任务创建、认领、完成与交接记录。 用药闭环:计划、未来 7 天提醒实例、确认服用、延后、异常反馈。 风险闭环:红橙黄蓝分级、30 分钟去重、推送、认领、处理与误报记录。 报平安:家属发起、老人语音回应、状态同步。 Web 导航升级为守护、家庭、照护、陪伴、我的。 设备接口已升级为设备令牌认证,UUID 不再单独承担认证。
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { requireDeviceAuth } from "@/lib/device-auth";
|
||||
import { getDeviceAgenda } from "@/lib/family-guard";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const deviceUuid = new URL(request.url).searchParams.get("deviceUuid") || "";
|
||||
try {
|
||||
await requireDeviceAuth(request, deviceUuid);
|
||||
const agenda = await getDeviceAgenda(deviceUuid);
|
||||
return NextResponse.json({
|
||||
circleName: agenda.circle?.name || null,
|
||||
posts: agenda.posts.map((post) => ({ id: post.id, content: post.content, authorName: post.author?.nickname || post.author?.username || "家里人", createdAt: post.createdAt })),
|
||||
memos: agenda.memos.map((memo) => ({ id: memo.id, title: memo.title, content: memo.content, priority: memo.priority })),
|
||||
doses: agenda.doses.map((dose) => ({ id: dose.id, scheduledFor: dose.scheduledFor, status: dose.status, name: dose.medicationPlan.name, dosage: dose.medicationPlan.dosage, instructions: dose.medicationPlan.instructions })),
|
||||
checkIns: agenda.checkIns,
|
||||
});
|
||||
} catch (error) { return NextResponse.json({ error: error instanceof Error ? error.message : "读取今日照护事项失败。" }, { status: 401 }); }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { requireDeviceAuth } from "@/lib/device-auth";
|
||||
import { createSafetyAlertForDevice } from "@/lib/family-guard";
|
||||
import { sendGuardAlertPush } from "@/lib/push";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const body = await request.json().catch(() => ({})) as Record<string, unknown>;
|
||||
const deviceUuid = typeof body.deviceUuid === "string" ? body.deviceUuid : "";
|
||||
try {
|
||||
await requireDeviceAuth(request, deviceUuid);
|
||||
const result = await createSafetyAlertForDevice(deviceUuid, body);
|
||||
if (!result.deduplicated) void sendGuardAlertPush(result.alert.id).catch(() => undefined);
|
||||
return NextResponse.json({ ok: true, alertId: result.alert.id, deduplicated: result.deduplicated });
|
||||
} catch (error) { return NextResponse.json({ error: error instanceof Error ? error.message : "创建风险提醒失败。" }, { status: 400 }); }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { GET } from "@/app/api/device/agenda/route";
|
||||
@@ -2,6 +2,7 @@ import { ConversationRole } from "@prisma/client";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { recordConversationTurns } from "@/lib/monitor-data";
|
||||
import { requireDeviceAuth } from "@/lib/device-auth";
|
||||
|
||||
function toConversationRole(rawRole: unknown) {
|
||||
switch (rawRole) {
|
||||
@@ -55,6 +56,7 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
try {
|
||||
await requireDeviceAuth(request, body.deviceUuid);
|
||||
const result = await recordConversationTurns({
|
||||
deviceUuid: body.deviceUuid,
|
||||
sessionId: typeof body.sessionId === "string" ? body.sessionId : undefined,
|
||||
@@ -71,4 +73,4 @@ export async function POST(request: Request) {
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { CheckInStatus, MedicationDoseStatus } from "@prisma/client";
|
||||
import { NextResponse } from "next/server";
|
||||
import { requireDeviceAuth } from "@/lib/device-auth";
|
||||
import { respondCheckIn, updateMedicationDose } from "@/lib/family-guard";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const body = await request.json().catch(() => ({})) as Record<string, unknown>;
|
||||
const deviceUuid = typeof body.deviceUuid === "string" ? body.deviceUuid : "";
|
||||
try {
|
||||
await requireDeviceAuth(request, deviceUuid);
|
||||
if (body.type === "MEDICATION") {
|
||||
const allowed = Object.values(MedicationDoseStatus);
|
||||
const status = allowed.includes(body.status as MedicationDoseStatus) ? body.status as MedicationDoseStatus : MedicationDoseStatus.NEEDS_FAMILY_CONFIRMATION;
|
||||
const dose = await updateMedicationDose(deviceUuid, String(body.doseId || ""), status, body.note, Number(body.snoozeMinutes || 10));
|
||||
return NextResponse.json({ ok: true, dose });
|
||||
}
|
||||
if (body.type === "CHECK_IN") {
|
||||
const status = body.status === CheckInStatus.SAFE
|
||||
? CheckInStatus.SAFE
|
||||
: body.status === CheckInStatus.DECLINED
|
||||
? CheckInStatus.DECLINED
|
||||
: CheckInStatus.NEEDS_CONTACT;
|
||||
const checkIn = await respondCheckIn(deviceUuid, String(body.checkInId || ""), status, body.responseText);
|
||||
return NextResponse.json({ ok: true, checkIn });
|
||||
}
|
||||
throw new Error("不支持的设备事件类型。");
|
||||
} catch (error) { return NextResponse.json({ error: error instanceof Error ? error.message : "保存照护反馈失败。" }, { status: 400 }); }
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { markFamilyMessagesAsRead } from "@/lib/monitor-data";
|
||||
import { requireDeviceAuth } from "@/lib/device-auth";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const body = (await request.json().catch(() => null)) as
|
||||
@@ -24,6 +25,7 @@ export async function POST(request: Request) {
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
await requireDeviceAuth(request, body.deviceUuid);
|
||||
await markFamilyMessagesAsRead(body.deviceUuid, messageIds);
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
@@ -36,4 +38,4 @@ export async function POST(request: Request) {
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
import { listFamilyMessagesForDevice } from "@/lib/monitor-data";
|
||||
import { requireDeviceAuth } from "@/lib/device-auth";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const rawDeviceValue =
|
||||
@@ -20,6 +21,7 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
try {
|
||||
await requireDeviceAuth(request, rawDeviceValue);
|
||||
const data = await listFamilyMessagesForDevice(rawDeviceValue, take);
|
||||
|
||||
return NextResponse.json({
|
||||
@@ -45,4 +47,4 @@ export async function GET(request: NextRequest) {
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { createBindUrl, ensureDeviceRegistration } from "@/lib/monitor-data";
|
||||
import { createDeviceToken, hashDeviceToken } from "@/lib/device-auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const body = (await request.json().catch(() => null)) as
|
||||
@@ -8,6 +10,7 @@ export async function POST(request: Request) {
|
||||
deviceUuid?: string;
|
||||
displayName?: string;
|
||||
appVersion?: string;
|
||||
deviceToken?: string;
|
||||
}
|
||||
| null;
|
||||
|
||||
@@ -26,10 +29,20 @@ export async function POST(request: Request) {
|
||||
appVersion:
|
||||
typeof body.appVersion === "string" ? body.appVersion : undefined,
|
||||
});
|
||||
const suppliedToken = typeof body.deviceToken === "string" ? body.deviceToken.trim() : "";
|
||||
const tokenMatches = suppliedToken && device.deviceTokenHash === hashDeviceToken(suppliedToken);
|
||||
if (device.deviceTokenHash && !tokenMatches) {
|
||||
return NextResponse.json({ error: "设备认证失败,不能覆盖已注册设备。" }, { status: 401 });
|
||||
}
|
||||
const deviceToken = tokenMatches ? suppliedToken : createDeviceToken();
|
||||
if (!device.deviceTokenHash) {
|
||||
await prisma.elderDevice.update({ where: { id: device.id }, data: { deviceTokenHash: hashDeviceToken(deviceToken) } });
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
deviceUuid: device.deviceUuid,
|
||||
bindUrl: createBindUrl(device.deviceUuid),
|
||||
deviceToken,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
@@ -40,4 +53,4 @@ export async function POST(request: Request) {
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ToolCallStatus } from "@prisma/client";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { recordToolCall } from "@/lib/monitor-data";
|
||||
import { requireDeviceAuth } from "@/lib/device-auth";
|
||||
|
||||
function toToolCallStatus(rawStatus: unknown) {
|
||||
return rawStatus === ToolCallStatus.FAILURE
|
||||
@@ -49,6 +50,7 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
try {
|
||||
await requireDeviceAuth(request, body.deviceUuid);
|
||||
const log = await recordToolCall({
|
||||
deviceUuid: body.deviceUuid,
|
||||
toolName: body.toolName,
|
||||
@@ -70,4 +72,4 @@ export async function POST(request: Request) {
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { UsageEventType } from "@prisma/client";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { recordUsageEvent } from "@/lib/monitor-data";
|
||||
import { requireDeviceAuth } from "@/lib/device-auth";
|
||||
|
||||
function toUsageEventType(rawType: unknown) {
|
||||
switch (rawType) {
|
||||
@@ -46,6 +47,7 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
try {
|
||||
await requireDeviceAuth(request, body.deviceUuid);
|
||||
await recordUsageEvent({
|
||||
deviceUuid: body.deviceUuid,
|
||||
eventType: toUsageEventType(body.eventType),
|
||||
@@ -62,4 +64,4 @@ export async function POST(request: Request) {
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user