feat: 项目总群自动建群+任务负责人后台视角+客户群活码进群+任务编辑

- 项目总群:≥3 人 appchat/create 自动建群(chatid=pgc+projectId),成员 diff 增删同步;DROP project_group_chats;推送改 appchat/send 群发、无群降级逐人
- 任务负责人:tasks.owner_userid(新建/编辑任务通讯录选人),task_set_owner 分配即预建 tgc 任务群发介绍,群主链接只归属负责人
- 任务编辑 task_update:基础字段+截图任务数量/关键词/说明,contents 调平只删 available 尾行
- 删除「一键推送」:task-one-click-push-service 整体移除(recordCardAck 移入 wecom-callback-events)
- 负责人后台视角:users.wecom_userid 绑定,lib/owner-scope 行级过滤(undefined=全量/""=空范围/非空=过滤),~30 action 补 managerGate/taskOwnerGate,owner 登录仅见工作台/任务中心/数据回收/复盘报表
- 任务绑定客户群+进群活码:tasks 增 customer_chat_id/name、join_config_id、join_qr_code 4 列,join_way add/update/get/del(换绑走 update 保 config_id 活码不变),后台「客户群」弹窗,portal 认领后扫码自助进群(满员自动开新群)
- 迁移 0013/0014/0015;催办支持按任务;企微可信域名 IP 验证文件
- 288 测试全绿;真机全链路验收通过(列群/活码四端点/portal 渲染/解绑清空)
This commit is contained in:
ABAPPLO
2026-09-04 18:03:46 +08:00
parent 5430624a43
commit dd5b603149
44 changed files with 4655 additions and 2039 deletions

View File

@@ -33,6 +33,7 @@ import {
import {
createDistributionTask,
createScreenshotTask,
updateTask,
} from "../../../lib/task-service";
import {
DistributionReleaseError,
@@ -58,7 +59,19 @@ import {
pushTaskToGroupChats,
syncGroupChats,
} from "../../../lib/wecom-group-push-service";
import { isManagerRequest } from "../../../lib/user-auth";
import {
bindTaskCustomerChat,
listOwnerCustomerChats,
unbindTaskCustomerChat,
} from "../../../lib/task-customer-chat-service";
import { isManagerRequest, managerForbidden } from "../../../lib/user-auth";
import {
getOwnerScope,
isDistributionOwner,
isTaskOwner,
ownerForbidden,
ownerScopeUserid,
} from "../../../lib/owner-scope";
import { extractPublishUrl } from "../../../lib/publish-url";
import {
getAccountFieldRefreshStatus,
@@ -66,22 +79,17 @@ import {
} from "../../../lib/account-field-refresh-service";
import {
deleteProject,
ensureProjectMainChat,
findOrCreateGroupOwnerPartner,
getProjectDetail,
listGroupOwnerPartners,
listProjects,
ProjectError,
saveGroupOwnerPartner,
saveProject,
setProjectGroupChats,
} from "../../../lib/project-service";
import { assignTaskOwner } from "../../../lib/task-group-service";
import { pushProjectTask } from "../../../lib/project-push-service";
import {
findTodayTaskPushLog,
listBoundWecomPartners,
listTaskPushLogs,
OneClickPushError,
oneClickPushTask,
} from "../../../lib/task-one-click-push-service";
type ActionBody = {
action?: string;
@@ -93,14 +101,49 @@ function numberValue(value: unknown, fallback = 0) {
return Number.isFinite(parsed) ? parsed : fallback;
}
async function assignOwnerQuietly(taskId: string, ownerUserid: unknown) {
const owner = String(ownerUserid ?? "").trim();
if (!owner) return "";
try {
const assignment = await assignTaskOwner(
getRawDb(),
{ taskId, ownerUserid: owner },
resolveWecomConfig(env as unknown as WecomBindings),
);
return assignment.warning ?? "";
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
}
export async function POST(request: Request) {
if (!(await isAdminRequest(request))) return adminForbidden();
try {
await ensureSchema();
const body = (await request.json()) as ActionBody;
const db = getRawDb();
let ownerWarning = "";
const scope = await getOwnerScope(request);
const managerGate = () =>
scope.kind === "owner" ? managerForbidden() : null;
const taskOwnerGate = async (taskId: unknown) => {
if (scope.kind === "full") return null;
const id = String(taskId ?? "").trim();
return id && (await isTaskOwner(db, id, scope.wecomUserId))
? null
: ownerForbidden();
};
const distributionOwnerGate = async (distributionId: unknown) => {
if (scope.kind === "full") return null;
const id = String(distributionId ?? "").trim();
return id && (await isDistributionOwner(db, id, scope.wecomUserId))
? null
: ownerForbidden();
};
if (body.action === "inspect_feishu") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const source = await readFeishuSource(
String(body.feishuUrl ?? "").trim(),
env as unknown as FeishuBindings,
@@ -124,6 +167,8 @@ export async function POST(request: Request) {
}
if (body.action === "create_task") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const name = String(body.name ?? "").trim();
const brand = String(body.brand ?? "").trim();
const dueAt = String(body.dueAt ?? "").trim();
@@ -135,7 +180,7 @@ export async function POST(request: Request) {
{ status: 400 },
);
}
await createDistributionTask(
const creation = await createDistributionTask(
{
feishuUrl: String(body.feishuUrl ?? "").trim(),
name,
@@ -144,11 +189,15 @@ export async function POST(request: Request) {
platform,
contentFormat,
projectId: String(body.projectId ?? "").trim(),
ownerUserid: String(body.ownerUserid ?? "").trim(),
},
env as unknown as FeishuBindings,
);
ownerWarning = await assignOwnerQuietly(creation.taskId, body.ownerUserid);
} else if (body.action === "create_screenshot_task") {
await createScreenshotTask({
const forbidden = managerGate();
if (forbidden) return forbidden;
const creation = await createScreenshotTask({
name: String(body.name ?? "").trim(),
brand: String(body.brand ?? "").trim(),
dueAt: String(body.dueAt ?? "").trim(),
@@ -157,8 +206,12 @@ export async function POST(request: Request) {
quantity: numberValue(body.quantity),
exampleImageKey: String(body.exampleImageKey ?? "").trim(),
projectId: String(body.projectId ?? "").trim(),
ownerUserid: String(body.ownerUserid ?? "").trim(),
});
ownerWarning = await assignOwnerQuietly(creation.taskId, body.ownerUserid);
} else if (body.action === "claim") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const partnerId = String(body.partnerId ?? "");
const taskId = String(body.taskId ?? "");
const quantity = Math.max(1, Math.min(50, numberValue(body.quantity, 1)));
@@ -361,6 +414,8 @@ export async function POST(request: Request) {
"account enrichment after publish URL update",
);
} else if (body.action === "save_collection_schedule") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const taskId = String(body.taskId ?? "").trim();
const startDate = String(body.startDate ?? "").trim();
const days = Array.isArray(body.days)
@@ -439,6 +494,8 @@ export async function POST(request: Request) {
).catch(() => undefined);
runInBackground(catchup, "collection catchup");
} else if (body.action === "run_due_collections") {
const forbidden = managerGate();
if (forbidden) return forbidden;
await runDueScheduledCollections(
db,
Date.now(),
@@ -451,6 +508,8 @@ export async function POST(request: Request) {
body.action === "collect_now" ||
body.action === "collect"
) {
const forbidden = managerGate();
if (forbidden) return forbidden;
const distributionId = String(body.distributionId ?? "");
const rawDay =
body.day === null || body.day === undefined
@@ -492,6 +551,8 @@ export async function POST(request: Request) {
),
);
} else if (body.action === "retry_failed_collections") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const taskId = String(body.taskId ?? "").trim();
if (!taskId) {
return Response.json({ error: "请选择需要补采的任务" }, { status: 400 });
@@ -517,6 +578,8 @@ export async function POST(request: Request) {
),
);
} else if (body.action === "backfill_account_profiles") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const backfill = backfillAccountProfiles(
db,
resolveCollectionMcpConfig(
@@ -526,6 +589,8 @@ export async function POST(request: Request) {
).catch(() => undefined);
runInBackground(backfill, "account profile backfill");
} else if (body.action === "set_public_account_ids") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const items = Array.isArray(body.items) ? body.items.slice(0, 50) : [];
const normalized = items
.map((item) => {
@@ -564,6 +629,8 @@ export async function POST(request: Request) {
),
);
} else if (body.action === "manual_metrics") {
const forbidden = await distributionOwnerGate(body.distributionId);
if (forbidden) return forbidden;
const distributionId = String(body.distributionId ?? "");
const exposure = Math.max(0, numberValue(body.exposure));
const views = Math.max(0, numberValue(body.views));
@@ -580,6 +647,8 @@ export async function POST(request: Request) {
.bind(exposure, views, distributionId)
.run();
} else if (body.action === "bind_wecom_external_id") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const partnerId = String(body.partnerId ?? "").trim().slice(0, 80);
const externalId = String(body.wecomExternalUserId ?? "")
.trim()
@@ -612,6 +681,8 @@ export async function POST(request: Request) {
wecomName,
});
} else if (body.action === "send_test_wecom") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const wecomConfig = resolveWecomConfig(
env as unknown as WecomBindings,
);
@@ -662,16 +733,23 @@ export async function POST(request: Request) {
dueDays: wecomConfig.dueDays,
});
} else if (body.action === "trigger_wecom_due_soon") {
const taskId = String(body.taskId ?? "").trim();
const forbidden = await taskOwnerGate(taskId || undefined);
if (forbidden) return forbidden;
const wecomConfig = resolveWecomConfig(
env as unknown as WecomBindings,
);
const summary = await runDueSoonWecomNotifications(
db,
wecomConfig,
undefined,
undefined,
taskId || undefined,
);
return Response.json(summary);
} else if (body.action === "trigger_wecom_overdue_escalation") {
if (!isManagerRequest(request)) return adminForbidden();
const forbidden = managerGate();
if (forbidden) return forbidden;
const wecomConfig = resolveWecomConfig(
env as unknown as WecomBindings,
);
@@ -679,24 +757,32 @@ export async function POST(request: Request) {
const autoRelease = await runOverdueAutoRelease(db, wecomConfig);
return Response.json({ escalation, autoRelease });
} else if (body.action === "wecom_list_external_contacts") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const wecomConfig = resolveWecomConfig(
env as unknown as WecomBindings,
);
const contacts = await listExternalContacts(wecomConfig);
return Response.json({ contacts });
} else if (body.action === "wecom_list_internal_contacts") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const wecomConfig = resolveWecomConfig(
env as unknown as WecomBindings,
);
const contacts = await listInternalContacts(wecomConfig);
return Response.json({ contacts });
} else if (body.action === "wecom_sync_group_chats") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const wecomConfig = resolveWecomConfig(
env as unknown as WecomBindings,
);
const groups = await syncGroupChats(db, wecomConfig);
return Response.json({ groups, syncedCount: groups.length });
} else if (body.action === "wecom_push_task_to_groups") {
const forbidden = await taskOwnerGate(body.taskId);
if (forbidden) return forbidden;
const wecomConfig = resolveWecomConfig(
env as unknown as WecomBindings,
);
@@ -725,34 +811,102 @@ export async function POST(request: Request) {
});
} else if (body.action === "wecom_task_group_pushes") {
const taskId = String(body.taskId ?? "").trim();
const forbidden = await taskOwnerGate(taskId || undefined);
if (forbidden) return forbidden;
const pushes = await listGroupPushes(db, taskId || undefined);
return Response.json({ pushes });
} else if (body.action === "project_list") {
const forbidden = managerGate();
if (forbidden) return forbidden;
return Response.json({ projects: await listProjects(db) });
} else if (body.action === "project_detail") {
const forbidden = managerGate();
if (forbidden) return forbidden;
return Response.json(await getProjectDetail(db, String(body.projectId ?? "")));
} else if (body.action === "project_save") {
return Response.json({
project: await saveProject(db, {
const forbidden = managerGate();
if (forbidden) return forbidden;
const saved = await saveProject(
db,
{
id: body.id,
name: body.name,
brand: body.brand,
mainGroupMemberIds: body.mainGroupMemberIds,
ownerUserid: body.ownerUserid,
note: body.note,
}),
});
},
resolveWecomConfig(env as unknown as WecomBindings),
);
return Response.json(saved);
} else if (body.action === "project_delete") {
const forbidden = managerGate();
if (forbidden) return forbidden;
await deleteProject(db, String(body.projectId ?? ""));
return Response.json({ ok: true });
} else if (body.action === "project_set_group_chats") {
const detail = await setProjectGroupChats(
} else if (body.action === "project_ensure_main_chat") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const result = await ensureProjectMainChat(
db,
String(body.projectId ?? ""),
Array.isArray(body.items) ? body.items : [],
resolveWecomConfig(env as unknown as WecomBindings),
);
return Response.json(detail);
return Response.json(result);
} else if (body.action === "task_set_owner") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const taskId = String(body.taskId ?? "").trim();
const ownerUserid = String(body.ownerUserid ?? "").trim();
if (!taskId) {
return Response.json({ error: "缺少 taskId" }, { status: 400 });
}
if (!ownerUserid) {
return Response.json({ error: "请选择任务负责人" }, { status: 400 });
}
const existing = await db
.prepare("SELECT id FROM tasks WHERE id = ?")
.bind(taskId)
.first<{ id: string }>();
if (!existing) {
return Response.json({ error: "任务不存在" }, { status: 404 });
}
const assignment = await assignTaskOwner(
db,
{ taskId, ownerUserid },
resolveWecomConfig(env as unknown as WecomBindings),
);
const task = await db
.prepare("SELECT owner_userid, task_chat_id FROM tasks WHERE id = ?")
.bind(taskId)
.first<{ owner_userid: string; task_chat_id: string }>();
return Response.json({ ok: true, assignment, task });
} else if (body.action === "task_update") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const platform = body.platform === "抖音" || body.platform === "小红书"
? (body.platform as "小红书" | "抖音")
: undefined;
const contentFormat = body.contentFormat === "video" || body.contentFormat === "image_text"
? (body.contentFormat as "image_text" | "video")
: undefined;
const update = await updateTask({
taskId: String(body.taskId ?? ""),
name: String(body.name ?? ""),
brand: String(body.brand ?? ""),
dueAt: String(body.dueAt ?? ""),
projectId: String(body.projectId ?? ""),
platform,
contentFormat,
quantity: numberValue(body.quantity),
keyword: String(body.keyword ?? ""),
instructions: String(body.instructions ?? ""),
exampleImageKey: String(body.exampleImageKey ?? ""),
});
ownerWarning = await assignOwnerQuietly(update.taskId, body.ownerUserid);
} else if (body.action === "partner_save") {
const forbidden = managerGate();
if (forbidden) return forbidden;
return Response.json({
partner: await saveGroupOwnerPartner(db, {
id: body.id,
@@ -761,8 +915,12 @@ export async function POST(request: Request) {
}),
});
} else if (body.action === "partner_group_owners") {
const forbidden = managerGate();
if (forbidden) return forbidden;
return Response.json({ partners: await listGroupOwnerPartners(db) });
} else if (body.action === "project_push_task") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const wecomConfig = resolveWecomConfig(env as unknown as WecomBindings);
const outcome = await pushProjectTask(
db,
@@ -779,41 +937,15 @@ export async function POST(request: Request) {
...outcome,
appSent: outcome.appResults.length - appFailed.length,
appFailedCount: appFailed.length,
hint: "任务群走企业群发,群主需在企微客户端「群发助手」点击发送后,消息才会送达客户群",
hint: "任务群消息已直接发送至任务内部群(负责人与领取人即时可见)",
});
} else if (body.action === "wecom_bound_partners") {
const partners = await listBoundWecomPartners(db);
const taskId = String(body.taskId ?? "").trim();
const pushedToday = taskId
? await findTodayTaskPushLog(db, taskId, "task_open")
: null;
return Response.json({ partners, pushedToday });
} else if (body.action === "task_one_click_push") {
const wecomConfig = resolveWecomConfig(env as unknown as WecomBindings);
const outcome = await oneClickPushTask(
db,
{
taskId: body.taskId,
sendMainGroup: body.sendMainGroup,
sendKoc: body.sendKoc,
createGroupTemplate: body.createGroupTemplate,
selectedKocPartnerIds: body.selectedKocPartnerIds,
force: body.force,
text: body.text,
},
wecomConfig,
);
return Response.json({
...outcome,
hint: "KOC 直达消息已即时送达;任务群走企业群发,群主需在企微客户端「群发助手」点击发送后才会送达客户群",
});
} else if (body.action === "task_push_logs") {
const logs = await listTaskPushLogs(db, String(body.taskId ?? "").trim());
return Response.json({ logs });
} else if (body.action === "task_report_list") {
if (!isManagerRequest(request)) return adminForbidden();
return Response.json({ reports: await getTaskReportData(db) });
return Response.json({
reports: await getTaskReportData(db, ownerScopeUserid(scope)),
});
} else if (body.action === "refresh_account_fields") {
const forbidden = managerGate();
if (forbidden) return forbidden;
const status = startAccountFieldRefresh(
db,
resolveCollectionMcpConfig(env as unknown as CollectionMcpBindings),
@@ -821,12 +953,16 @@ export async function POST(request: Request) {
);
return Response.json({ refresh: status });
} else if (body.action === "account_field_refresh_status") {
const forbidden = managerGate();
if (forbidden) return forbidden;
return Response.json({ refresh: getAccountFieldRefreshStatus() });
} else if (body.action === "task_group_links") {
const taskId = String(body.taskId ?? "").trim();
const forbidden = await taskOwnerGate(taskId);
if (forbidden) return forbidden;
const task = await db
.prepare(
"SELECT id, name, share_token, task_chat_id FROM tasks WHERE id = ?",
"SELECT id, name, share_token, task_chat_id, owner_userid FROM tasks WHERE id = ?",
)
.bind(taskId)
.first<{
@@ -834,71 +970,162 @@ export async function POST(request: Request) {
name: string;
share_token: string | null;
task_chat_id: string;
owner_userid: string;
}>();
if (!task) return Response.json({ error: "任务不存在" }, { status: 404 });
const portalUrl = String(env.KOC_PORTAL_URL ?? "").replace(/\/$/, "");
const owners = await listGroupOwnerPartners(db);
const ownerUserid = String(task.owner_userid ?? "").trim();
let owners: {
partnerId: string;
name: string;
wecomUserid: string;
isInternal: boolean;
link: string;
}[] = [];
if (task.share_token && ownerUserid) {
try {
const partner = await findOrCreateGroupOwnerPartner(db, ownerUserid);
owners = [
{
partnerId: partner.id,
name: partner.name,
wecomUserid: partner.wecom_userid,
isInternal: Boolean(partner.wecom_userid),
link: `${portalUrl}/?task=${task.share_token}&owner=${partner.id}`,
},
];
} catch {
owners = [];
}
}
return Response.json({
taskName: task.name,
taskChatId: task.task_chat_id ?? "",
claimUrl: task.share_token
? `${portalUrl}/?task=${task.share_token}`
: "",
owners: task.share_token
? owners.map((owner) => ({
partnerId: owner.id,
name: owner.name,
wecomUserid: owner.wecom_userid,
isInternal: Boolean(owner.wecom_userid),
link: `${portalUrl}/?task=${task.share_token}&owner=${owner.id}`,
}))
: [],
owners,
});
} else if (body.action === "task_customer_chats") {
const taskId = String(body.taskId ?? "").trim();
const forbidden = await taskOwnerGate(taskId);
if (forbidden) return forbidden;
const task = await db
.prepare(
"SELECT id, owner_userid, customer_chat_id FROM tasks WHERE id = ?",
)
.bind(taskId)
.first<{ id: string; owner_userid: string; customer_chat_id: string }>();
if (!task) return Response.json({ error: "任务不存在" }, { status: 404 });
const ownerUserid = (
scope.kind === "full"
? String(task.owner_userid ?? "")
: scope.wecomUserId
).trim();
if (!ownerUserid) {
return Response.json(
{ error: "请先设置任务负责人,再绑定客户群" },
{ status: 400 },
);
}
const wecomConfig = resolveWecomConfig(env as unknown as WecomBindings);
const chats = await listOwnerCustomerChats(wecomConfig, ownerUserid);
return Response.json({
ownerUserid,
chats,
boundChatId: String(task.customer_chat_id ?? ""),
});
} else if (body.action === "task_bind_customer_chat") {
const taskId = String(body.taskId ?? "").trim();
const forbidden = await taskOwnerGate(taskId);
if (forbidden) return forbidden;
const task = await db
.prepare(
"SELECT id, owner_userid, customer_chat_id FROM tasks WHERE id = ?",
)
.bind(taskId)
.first<{ id: string; owner_userid: string; customer_chat_id: string }>();
if (!task) return Response.json({ error: "任务不存在" }, { status: 404 });
const ownerUserid = (
scope.kind === "full"
? String(task.owner_userid ?? "")
: scope.wecomUserId
).trim();
const chatId = String(body.chatId ?? "").trim();
if (chatId && !ownerUserid) {
return Response.json(
{ error: "请先设置任务负责人,再绑定客户群" },
{ status: 400 },
);
}
const wecomConfig = resolveWecomConfig(env as unknown as WecomBindings);
if (chatId) {
const chatName = String(body.chatName ?? "").trim();
const result = await bindTaskCustomerChat(
db,
{ taskId, chatId, chatName },
wecomConfig,
);
return Response.json({ task: result });
}
await unbindTaskCustomerChat(db, taskId, wecomConfig);
return Response.json({
task: {
customerChatId: "",
customerChatName: "",
joinConfigId: "",
joinQrCode: "",
},
});
} else if (body.action === "task_group_push") {
const taskId = String(body.taskId ?? "").trim();
const task = await db
.prepare(
"SELECT id, name, share_token FROM tasks WHERE id = ?",
"SELECT id, name, share_token, owner_userid FROM tasks WHERE id = ?",
)
.bind(taskId)
.first<{ id: string; name: string; share_token: string | null }>();
.first<{
id: string;
name: string;
share_token: string | null;
owner_userid: string;
}>();
if (!task) return Response.json({ error: "任务不存在" }, { status: 404 });
const forbidden = await taskOwnerGate(taskId);
if (forbidden) return forbidden;
if (!task.share_token) {
return Response.json({ error: "任务缺少领取链接" }, { status: 400 });
}
const owners = (await listGroupOwnerPartners(db)).filter(
(owner) => owner.wecom_userid,
);
if (owners.length === 0) {
const ownerUserid = String(task.owner_userid ?? "").trim();
if (!ownerUserid) {
return Response.json(
{ error: "没有可推送的内部群主,请先在后台登记群主 userid" },
{ error: "该任务未分配负责人,请先在任务中心分配" },
{ status: 400 },
);
}
const owner = await findOrCreateGroupOwnerPartner(db, ownerUserid);
const portalUrl = String(env.KOC_PORTAL_URL ?? "").replace(/\/$/, "");
const wecomConfig = resolveWecomConfig(env as unknown as WecomBindings);
let sent = 0;
let failed = 0;
const failList: string[] = [];
for (const owner of owners) {
const link = `${portalUrl}/?task=${task.share_token}&owner=${owner.id}`;
try {
const result = await sendWecomAppMessage(
[owner.wecom_userid],
`【任务派发】${task.name}\n请把领取链接转发给你的 KOC(链接已绑定你的群主身份):\n${link}`,
wecomConfig,
);
if (result.skipped) {
failed += 1;
failList.push(owner.name);
} else {
sent += result.sent;
failed += result.failed;
}
} catch {
const link = `${portalUrl}/?task=${task.share_token}&owner=${owner.id}`;
try {
const result = await sendWecomAppMessage(
[owner.wecom_userid],
`【任务派发】${task.name}\n请把领取链接转发给你的 KOC(链接已绑定你的群主身份):\n${link}`,
wecomConfig,
);
if (result.skipped) {
failed += 1;
failList.push(owner.name);
} else {
sent += result.sent;
failed += result.failed;
}
} catch {
failed += 1;
failList.push(owner.name);
}
return Response.json({ sent, failed, failList });
} else if (body.action === "wecom_group_chats") {
@@ -908,7 +1135,10 @@ export async function POST(request: Request) {
return Response.json({ error: "不支持的操作" }, { status: 400 });
}
return Response.json(await getDashboardData());
const dashboard = await getDashboardData(ownerScopeUserid(scope));
return Response.json(
ownerWarning ? { ...dashboard, ownerWarning } : dashboard,
);
} catch (error) {
return Response.json(
{ error: error instanceof Error ? error.message : "操作失败" },
@@ -917,8 +1147,7 @@ export async function POST(request: Request) {
error instanceof FeishuSourceError ||
error instanceof DistributionReleaseError ||
error instanceof WecomClientError ||
error instanceof ProjectError ||
error instanceof OneClickPushError
error instanceof ProjectError
? error.status
: 500,
},