feat(channels): 按账号连接/断开 + 可删空 id 条目(修主人真机踩的两个缺口)
缺口①:「连接」是按平台连、且恒打第一条账号(channelActionRequest 只有 name)⇒ 主人有两条
qq-obv11 时点到的是 mode=server 的历史条目,报 server mode requires listenAddr,而正确的
client 条目轮不到。缺口②:空 id 条目既删不掉(DELETE 要求 id 非空)也改不了(UpsertChannel
空 id = 新建),越改越多。
后端:
- POST /admin/channels/open|close 请求体新增可选 accountId(缺省逐字兼容,老客户端零影响);
按该账号驱动;未知 id ⇒ 404 且零连接(不静默回落第一条);已配置未注册 ⇒ 409
- DELETE /admin/channels/{platform}/config 新增显式 empty=1(id 与 empty=1 互斥 ⇒ 400;
无可删 ⇒ 404);语义严格 —— 只删空 id 条目,绝不把「id 为空」默认解释成删空 id
- 响应新增:open/close 的 resolved(实际驱动的适配器名)与 accountId;delete 的 removed/empty;
GET /admin/channels 的 accounts[].connected(可选 bool,缺省 = 未知)
- 审计动作名不变(channel.open/close/delete),日志行新增 accountId 字段
前端:每条账号一行「连接/断开」(带 accountId);按钮旁明确「会连哪一条」;空 id 行显著标注
+ 删除入口;channelActionBody 携带 accountId;新增 action 测试 33 checks + 空 id 行渲染 31 checks。
测试:后端 14 个用例(真实 HTTP 路由 + JWT + 真注册表 + spy 适配器,断言「哪一条被驱动、
另一条一次都没被碰」)。
变异 6 条(accountId 被忽略 / id 为空当删空 id / 找不到静默回落 / 前端丢 accountId /
删空 id 退化成 ?id= / 空 id 行按普通行渲染)。
This commit is contained in:
1 parent
c4ea02a887
commit
42279b7124
21 files changed
+1719
-164
No files matched your search
@@ -15,20 +15,43 @@ export interface ChannelConfigSaveResult {
|
||||
path: string;
|
||||
platform: string;
|
||||
accountId: string;
|
||||
/** 仅 `?empty=1` 的删除会带上:本次实际删掉的无 id 条目数。 */
|
||||
removed?: number;
|
||||
empty?: boolean;
|
||||
}
|
||||
|
||||
/** POST /api/v1/admin/channels/open — connects a registered channel adapter. */
|
||||
export function adminChannelOpen(name: string): Promise<{ name: string; connected: boolean }> {
|
||||
return request<{ name: string; connected: boolean }>("/admin/channels/open", {
|
||||
/** Result of POST /admin/channels/open|close. `resolved` is the adapter that was
|
||||
* actually driven (`qq-obv11:9616b27a`), which is what the panel must show —
|
||||
* the request's `name` alone never tells a multi-account platform which account
|
||||
* was touched. */
|
||||
export interface ChannelActionResult {
|
||||
name: string;
|
||||
resolved?: string;
|
||||
accountId?: string;
|
||||
connected: boolean;
|
||||
}
|
||||
|
||||
/** open/close 的请求体。**accountId 缺省时逐字维持旧契约 `{name}`**(老后端/老客户端
|
||||
* 仍互通);给出时才是多账号定位,后端据此**只**驱动该账号。 */
|
||||
export function channelActionBody(name: string, accountId?: string): { name: string; accountId?: string } {
|
||||
const id = accountId?.trim();
|
||||
return id ? { name, accountId: id } : { name };
|
||||
}
|
||||
|
||||
/** POST /api/v1/admin/channels/open — connects a registered channel adapter.
|
||||
* Pass `accountId` on a multi-account platform so the panel connects the account
|
||||
* the user actually clicked instead of whatever the backend picks first. */
|
||||
export function adminChannelOpen(name: string, accountId?: string): Promise<ChannelActionResult> {
|
||||
return request<ChannelActionResult>("/admin/channels/open", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ name }),
|
||||
body: JSON.stringify(channelActionBody(name, accountId)),
|
||||
});
|
||||
}
|
||||
/** POST /api/v1/admin/channels/close — disconnects a registered channel adapter. */
|
||||
export function adminChannelClose(name: string): Promise<{ name: string; connected: boolean }> {
|
||||
return request<{ name: string; connected: boolean }>("/admin/channels/close", {
|
||||
export function adminChannelClose(name: string, accountId?: string): Promise<ChannelActionResult> {
|
||||
return request<ChannelActionResult>("/admin/channels/close", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ name }),
|
||||
body: JSON.stringify(channelActionBody(name, accountId)),
|
||||
});
|
||||
}
|
||||
/** POST /api/v1/admin/channels/test — structural identity test (actor × whitelist). */
|
||||
@@ -88,6 +111,15 @@ export function adminDeleteChannelAccount(platform: string, id: string): Promise
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
/** DELETE /api/v1/admin/channels/{platform}/config?empty=1 — 删除该平台**无 id 的历史条目**
|
||||
* (面板既改不动也删不掉它们:带空 id 保存会被后端当成新账号另开一条)。
|
||||
* 这个 `empty=1` 是**显式**的:绝不能靠「id 为空」来表达同一意图,那样一次误发的
|
||||
* 空请求就会静默批量删除。 */
|
||||
export function adminDeleteEmptyChannelAccounts(platform: string): Promise<ChannelConfigSaveResult> {
|
||||
return request<ChannelConfigSaveResult>(`/admin/channels/${platform}/config?empty=1`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
/** GET /api/v1/admin/channels/{platform}/view — one platform account's aggregated
|
||||
* panel data (friends / servers / channels / members / private chats), owner only. */
|
||||
export function adminChannelPlatformView(platform: string, accountId?: string): Promise<KookViewResponse> {
|
||||
|
||||
Reference in new issue
Block a user