fix: 完整对账 Sub2API 公告字段
This commit is contained in:
@@ -167,7 +167,8 @@ function renderAnnouncements(response: Record<string, unknown>): RenderedCliResu
|
||||
if (items.length > 0) lines.push("", renderTable([["ID", "STATUS", "NOTIFY", "TITLE", "STARTS_AT", "ENDS_AT"], ...items.map((item) => [text(item.id), text(item.status), text(item.notify_mode), text(item.title), text(item.starts_at), text(item.ends_at)])]));
|
||||
} else {
|
||||
const announcement = record(report.announcement);
|
||||
lines.push(`WRITE attempted=${text(report.writeAttempted)} reconciled=${text(report.reconciled)} id=${text(announcement.id)}`);
|
||||
const mismatchedFields = Array.isArray(report.mismatchedFields) ? report.mismatchedFields.join(",") : "-";
|
||||
lines.push(`WRITE attempted=${text(report.writeAttempted)} reconciled=${text(report.reconciled)} id=${text(announcement.id)} mismatched-fields=${mismatchedFields || "-"}`);
|
||||
lines.push(`TITLE ${text(announcement.title)}`, `STATUS ${text(announcement.status)} NOTIFY_MODE ${text(announcement.notify_mode)}`, `STARTS_AT ${text(announcement.starts_at)} ENDS_AT ${text(announcement.ends_at)}`, `TARGETING ${JSON.stringify(record(announcement.targeting))}`, "CONTENT", String(announcement.content ?? ""));
|
||||
}
|
||||
if (typeof report.error === "string") lines.push(`ERROR ${text(report.error)}`);
|
||||
@@ -183,7 +184,7 @@ function renderAnnouncementsHelp(): RenderedCliResult {
|
||||
"platform-infra sub2api codex-pool announcements list [--page 1] [--page-size 20] [--status draft|active|archived] [--search text] [--sort-by created_at] [--sort-order asc|desc] [--target PK01] [--json]",
|
||||
"platform-infra sub2api codex-pool announcements get --id <positive-integer> [--target PK01] [--json]",
|
||||
"platform-infra sub2api codex-pool announcements create --title <text> (--content <markdown>|--content-file <path>) [--announcement-status draft|active|archived] [--notify-mode silent|popup] [--targeting-json <json-object>] [--starts-at <unix-seconds|RFC3339>] [--ends-at <unix-seconds|RFC3339>] [--target PK01] [--confirm] [--json]",
|
||||
"create defaults to dry-run; only --confirm calls the native Sub2API create API and then reads the returned ID back.",
|
||||
"create defaults to dry-run; only --confirm calls the native Sub2API create API and then reconciles every planned announcement field after reading the returned ID back.",
|
||||
].join("\n");
|
||||
return { ok: true, command: "platform-infra sub2api codex-pool announcements --help", renderedText, contentType: "text/plain", projection: { ok: true, mutation: false } };
|
||||
}
|
||||
|
||||
@@ -30,6 +30,49 @@ def announcements_get(token, announcement_id):
|
||||
item = ensure_success(curl_api("GET", f"/api/v1/admin/announcements/{announcement_id}", bearer=token), "get announcement")
|
||||
return {"ok": isinstance(item, dict), "mode": "read-only", "mutation": False, "writeAttempted": 0, "announcement": item, "valuesPrinted": False}
|
||||
|
||||
def announcement_time_seconds(value):
|
||||
if value is None or value == "":
|
||||
return None
|
||||
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
||||
return int(value)
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
if parsed.tzinfo is None:
|
||||
parsed = parsed.replace(tzinfo=timezone.utc)
|
||||
return int(parsed.timestamp())
|
||||
except ValueError:
|
||||
return value
|
||||
return value
|
||||
|
||||
def announcement_targeting(value):
|
||||
if not isinstance(value, dict):
|
||||
return value
|
||||
normalized = dict(value)
|
||||
if normalized.get("any_of") is None:
|
||||
normalized["any_of"] = []
|
||||
return normalized
|
||||
|
||||
def announcements_reconcile(expected, actual):
|
||||
fields = ("title", "content", "status", "notify_mode", "targeting", "starts_at", "ends_at")
|
||||
expected_values = {}
|
||||
actual_values = {}
|
||||
mismatched_fields = []
|
||||
for field in fields:
|
||||
expected_value = expected.get(field) if isinstance(expected, dict) else None
|
||||
actual_value = actual.get(field) if isinstance(actual, dict) else None
|
||||
if field in ("starts_at", "ends_at"):
|
||||
expected_value = announcement_time_seconds(expected_value)
|
||||
actual_value = announcement_time_seconds(actual_value)
|
||||
elif field == "targeting":
|
||||
expected_value = announcement_targeting(expected_value)
|
||||
actual_value = announcement_targeting(actual_value)
|
||||
expected_values[field] = expected_value
|
||||
actual_values[field] = actual_value
|
||||
if expected_value != actual_value:
|
||||
mismatched_fields.append(field)
|
||||
return {"expected": expected_values, "actual": actual_values, "mismatchedFields": mismatched_fields}
|
||||
|
||||
def announcements_create(token, payload):
|
||||
announcement = payload.get("announcement") if isinstance(payload.get("announcement"), dict) else {}
|
||||
created = ensure_success(curl_api("POST", "/api/v1/admin/announcements", bearer=token, payload=announcement), "create announcement")
|
||||
@@ -37,10 +80,17 @@ def announcements_create(token, payload):
|
||||
if not isinstance(announcement_id, int) or announcement_id <= 0:
|
||||
raise RuntimeError("create announcement response missing positive id")
|
||||
actual = ensure_success(curl_api("GET", f"/api/v1/admin/announcements/{announcement_id}", bearer=token), "read announcement after create")
|
||||
reconciled = isinstance(actual, dict) and actual.get("id") == announcement_id
|
||||
reconciliation = announcements_reconcile(announcement, actual)
|
||||
id_matched = isinstance(actual, dict) and actual.get("id") == announcement_id
|
||||
mismatched_fields = list(reconciliation.get("mismatchedFields") or [])
|
||||
if not id_matched:
|
||||
mismatched_fields.insert(0, "id")
|
||||
reconciled = id_matched and len(mismatched_fields) == 0
|
||||
return {
|
||||
"ok": reconciled, "mode": "confirmed", "mutation": True, "writeAttempted": 1,
|
||||
"writeSucceeded": 1, "writeFailed": 0, "reconciled": reconciled,
|
||||
"mismatchedFields": mismatched_fields,
|
||||
"reconciliation": {**reconciliation, "idMatched": id_matched},
|
||||
"announcement": actual, "valuesPrinted": False,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user