Commit Graph

249 Commits

Author SHA1 Message Date
root 6e6e19cccb feat(schedule-tasks): support auto-publish via media accounts
Replace the legacy target_platform string on schedule tasks with a
workspace-aware auto-publish payload (auto_publish + publish_account_ids
JSONB + cover_asset_url + cover_image_asset_id) and migrate the schema,
sqlc queries, generated models, domain struct, ScheduleTaskService DTOs,
and dispatch worker to round-trip the new fields. PromptRuleGeneration
gains a WithPublishJobService hook so executeGeneration can enqueue an
auto-publish job once the article is ready, and worker-generate wires
the publish-job service in. On the admin-web side, extract
PublishArticleModal's account-card builders into a shared
publish-account-cards module, rebuild GenerateTaskDrawer's schedule
mode around account selection plus a CoverPickerModal, and surface the
new "auto publish" column on ScheduleTaskTab. Shared types and i18n
strings cover the new fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:32:19 +08:00
root c9267d2fae feat(auth): block disabled user accounts at the subscription guard
RequireActiveTenantSubscription now optionally takes an AuthRepository
and rejects the request with 40311/user_disabled when the actor's user
row is anything other than active. Wire the repo through the tenant
router, surface the new error code in admin-web's
membershipBlockedErrors set, persist a "disabled" membership status when
markStoredMembershipBlocked sees user_disabled, and add localized copy
on the blocked screen and shell membership banner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:31:26 +08:00
root e15806bd91 feat(template-wizard): require library brand before favoriting competitors
Drop the implicit brand-creation path in handleFavoriteCompetitor and
just refuse to save a competitor until the user has explicitly chosen a
library brand; clear saved state on competitor drafts when the brand is
unset. Also enforce the configured custom-outline label length cap and
auto-focus newly added outline nodes for faster keyboard editing. New
i18n strings cover the brand-required and outline-length messages plus
the updated competitor hint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:30:29 +08:00
root 14a7921445 feat(prompts): expand keyword guidance to long-tail search queries
Tighten the analyze prompts (runtime + recommendation platform) so the
model returns 8-10 question-style search queries (哪家好/有哪些/怎么选/
价格/排名/避坑 …) instead of generic tag words, and lift the keyword cap
in normalizeAnalyzeResult from 5 to 10 to keep them. Add tests covering
the new ten-keyword window and the question-like guidance in the seeded
analyze prompt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:28:59 +08:00
root 814ea52c0e feat(desktop-client): validate qiehao session via detected account
The 企鹅号 console returns 200 even when the session is silently expired,
so probe/silent-refresh/console-open now require detect() to return an
account whose identity matches the bound one. Surface a localized
"授权已过期" prompt in the renderer when the new
desktop_account_session_expired error bubbles up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:28:41 +08:00
root 9d9c051d6d feat(media): require cover image for dongchedi publish batches
懂车帝 has the same cover-mandatory rule as 百家号; treat them the same
when deciding whether a publish batch must carry a cover.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:28:23 +08:00
root f9ecb33d8e chore(knowledge): drop verbose snippet payloads from resolve log
Logging the full candidate and selected snippet bodies per resolve was
inflating log volume without adding signal we use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:28:04 +08:00
root 5216a4847c feat(login-ui): dedupe submissions and surface guard error codes
Collapse rapid double-submits into a single in-flight request across
admin-web, ops-web and the desktop client so users cannot fire parallel
login attempts and trip the new in-progress guard. Map the new
login_rate_limited / login_locked / login_in_progress / login_guard_unavailable
error codes to localized messages in every login surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:07:32 +08:00
root 19037a9e4b feat(auth): apply LoginGuard to tenant and ops login endpoints
Wire LoginGuard into tenant-api and ops-api login flows: acquire a
permit before checking credentials, record a failure on every invalid
attempt to drive lockouts, and clear counters on success. Tenant-api
now also forwards the request IP into the service so per-IP and
IP+identifier limits actually fire under real traffic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:07:16 +08:00
root a62c0e4b5d feat(auth): add LoginGuard for rate-limit and lockout primitives
Introduce a Redis-backed LoginGuard that combines per-IP, per-identifier
and IP+identifier rate limits, exponential lockout on consecutive failures,
and a concurrent-attempt lock so a single subject cannot pile up parallel
brute-force tries. The guard ships with an in-memory fallback when Redis
is unreachable so login protection keeps working under degraded mode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:06:49 +08:00
root 11bd4e4d4e feat(redis): keep services running when Redis is unreachable
Add a lazy Redis client constructor and an in-memory fallback for the
refresh-session store, plus tenant-api and ops-api bootstrap that warns
and continues with the lazy client instead of failing. Refresh and
blacklist operations now silently fall back to the in-memory store while
Redis is down so existing sessions stay valid until it recovers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 01:06:08 +08:00
root 2aa39d7a11 test(ops/kol-subscription): cover cache invalidation on admin actions
Verify approve/revoke/manual-bind delete the right tenant-scoped keys via
a recording cache double, so the prompt cache stays consistent with the
KOL subscription state changes shipped in the previous commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:08:08 +08:00
root 0729b76cc7 style(ops-web): unify table actions as circular icon buttons
Replace the mixed text-link / button action lists in Accounts,
AdminUsers, and SiteDomainMappings with a shared .table-actions-row +
.action-* class set in styles.css. Each row now renders compact icon
buttons (edit/delete/approve/revoke/key/kol/reset/play) tinted by intent,
matching the new KolSubscriptions console look.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:07:55 +08:00
root 73fd7db21a feat(ops-web/kol): add KOL subscription review console
New /kol-subscriptions page lists pending and active subscriptions, lets
operators approve / revoke / manually bind packages to tenants, and links
into the AppShell nav under 订阅包审批. Register Alert globally so the
view's status banners render.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:07:41 +08:00
root 9bb7144593 refactor(kol-admin): move subscription admin endpoints from tenant-api to ops-api
Approving / manually binding / revoking KOL subscriptions belonged to the
operations console, not the tenant-admin role on tenant-api. Add a fresh
KolSubscriptionService + repository + handlers under the ops module with
its own list/manual-bind/approve/revoke routes, wire a Redis-backed cache
into ops-api so admin actions can invalidate tenant prompt caches, and
delete the tenant-side admin service/handler now that ops-web owns the UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:07:25 +08:00
root 4dd8a1bb0e feat(tenant/kol): let KOLs self-subscribe to their own published packages
Add /kol/manage/packages/:id/self-subscription POST/DELETE on the tenant
side so a KOL can grant themselves access to their own published package
without going through the marketplace approval flow. Reject self-subscribe
through the marketplace with a clear error, expose owned_by_current_tenant
on package responses, and surface a self_subscription block on KolPackage
Response. The admin-web KOL workspace gets 订阅自己 / 取消订阅 entries with
matching messaging in the marketplace detail view.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:06:57 +08:00
root 088dbb0ec7 docs(self-test): mark 懂车帝 and 微信公众号 as CDP-driven flows
Annotate the two adapters that now run the publish step through the
embedded Chromium DevTools session so future debugging knows which
platforms bypass the plain HTTP path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:03:02 +08:00
root 6a56935fb9 chore(admin-web/login): drop redundant tenant admin login subheading
The 欢迎回来 headline already conveys context; the secondary
"Tenant Admin Login" line added noise on the simplified login card.
Remove the paragraph and the now-unused i18n key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:02:52 +08:00
root 2f3055babd feat(admin-web/articles): show partial-success publish status from records
ArticlePublishStatus now derives partial_success when both succeeded and
failed records are present, fetches publish_records eagerly with a 30s
stale window so the popover lights up without a click, and ArticleDetail
Drawer reuses the component instead of its own tag. Relabel partial_
success copy to 部分发布 / Partially Published to match the new bucket.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:02:36 +08:00
root e8c4fedb45 feat(tenant/articles): aggregate publish status from per-account records
a.publish_status only stored a coarse summary so the article list/detail
mis-reported as 失败 when one of several accounts failed. Compute the
effective status via a LATERAL join over latest publish_records per
platform_account_id and bucket into success / publishing / failed /
partial_success. Wire the same expression into list filtering and detail
queries, and let normalizePublishStatus / batch-status helpers retain
partial_success end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:02:03 +08:00
root 677cfc5aff docs(self-test): record weixin-gzh manual publish result
Mark 微信公众号 as passing for image/text/table after the new editor flow,
linked to the verification post used during the run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 19:57:27 +08:00
root 99a992f34b feat(ui/publish-records): surface 已存草稿 status and client-only manage hint
Render publish_type=draft tasks with the warn tone and 已存草稿 label, swap
the management link copy to 打开草稿箱 for weixin_gzh, and expand the task
summary to mention the publish_authorization_required reason. In the admin
drawer, stop falling back to external_manage_url for missing public links
and instead show a hint pointing operators back to the desktop client for
weixin_gzh / zol records.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 19:57:11 +08:00
root a09d44d8f0 feat(desktop/weixin-gzh): drive editor to publish with draft fallback on verify
Replace the masssend HTTP path with a Playwright-driven editor flow that
clicks "群发" and resolves the publish response, then polls appmsgpublish
for the public mp.weixin.qq.com/s URL. When the dialog asks for admin
verification / scan-code authorization, swallow it as a successful draft
save and surface fallback_reason=publish_authorization_required so the
operator can finish from the console. Also broaden token extraction in
account-binder and treat the draft list URL (cgi-bin/appmsg) as the
console landing page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 19:56:56 +08:00
root ed6b2c09fa fix(desktop/wangyihao): soften creative statement to a personal-opinion note
"内容由AI生成" tripped Wangyihao's AIGC review and parked drafts under
manual screening. Switch to "个人观点,仅供参考" so legitimate posts move
through normal moderation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 19:56:40 +08:00
root 2e5643ddfd feat(desktop/weixin-gzh): mass-send drafts and resolve public article URL
Drafts previously stopped at the草稿箱 step, leaving the operator to publish
manually. Chain a /cgi-bin/masssend call, then poll the appmsgpublish list
(by title and by latest) to extract the mp.weixin.qq.com/s public link
and a publish_id. Surface dedicated failures for publish and link-missing
states, and switch the result payload to publish_type="publish" with the
appmsgpublish manage URL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:12:49 +08:00
root 2501f9db85 feat(desktop/renderer): surface re-authorize affordance for expired accounts
Replace the generic 重新校验 action with state-aware copy and an arrow
icon when authState is expired, and route the click through the bind flow
so users land directly on the platform login page. AiPlatformsView gains
the same shortcut and an inline alert explaining tasks are paused until
re-auth completes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:12:20 +08:00
root b59435dec5 refactor(desktop/renderer): centralize platform label and short-name lookup
Move the per-platform translation map out of HomeView/TasksView and the
titleCaseToken fallback out of PublishManagementView into shared helpers
on media-catalog. Single source of truth keeps renderer views consistent
with the binding catalog when new platforms ship.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:12:05 +08:00
root 426e94dd6b feat(desktop/account-health): mark freshly bound accounts as live
Bind callbacks now stamp the runtime account as health=live with a fresh
verified_at and seed the health record via markTrackedAccountBound, so the
UI doesn't show a just-bound account as 尚未校验 while waiting for the next
probe. Also let force probes bypass the cached-result short-circuit so the
manual 重新校验 button always re-runs the probe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:11:50 +08:00
root e89349399b feat(desktop/weixin-gzh): probe authenticated console with token resolver
Naked /cgi-bin/home redirects to the login page even with a live cookie,
which made probe/refresh/verify mis-classify accounts as expired. Resolve
the tokenized console URL via mp.weixin.qq.com first, then read the page
state to detect 登录超时/未登录 banners and reuse detectWeixinGzh to confirm
identity. Open-console also now lands on the tokenized URL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:11:35 +08:00
root 8dcd60b604 fix(desktop/weixin-gzh): tolerate numeric appMsgId and classify login timeout
The MP draft API sometimes returns appMsgId as a number or under the
appmsgid alias; coerce both shapes before treating the response as a
success. Also extend the platform auth adapter to recognize 登录超时/
请重新登录 wording so the runtime drives the account back to re-auth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:11:19 +08:00
root 7ca79723cd fix(desktop/doubao): drop byteimg and bytednsdoc CDN URLs from cited sources
These ByteDance asset CDNs ship images and static docs that are not real
citations, so they pollute the monitoring source list. Filter them at both
buildSourceItem and dedupe stages, and add unit coverage via an exported
test-only helper bag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:11:04 +08:00
root bef55bd11f feat(ops-api/site-mappings): provision monitoring DB pool and readiness check
Open a separate pgx pool for the monitoring database, wire it into the
SiteDomainMappingService, and surface its health in /readyz. Point compose
at the dedicated monitoring-postgres service and align local/dev configs
with the per-instance ports. Also reformat the site_domain_mapping types
with gofmt while the files are touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:10:48 +08:00
root 665405763c feat(ops-web/site-mappings): add site domain mappings management view
Add a CRUD page for site_domain_mappings with search, pagination, create/
edit/enable/disable/delete actions, and register it in the AppShell nav.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:10:17 +08:00
root 9760bec8bd feat(ops/site-mappings): wire site domain mapping routes and monitoring DB
Register site-domain-mappings CRUD endpoints under the authenticated ops
router, add a monitoring_database config block (falling back to the main
ops database when unset) for the service to share the monitoring pool.
2026-04-29 16:02:11 +08:00
root 7ab9a64a0f feat(ops/site-mappings): add HTTP handlers for site domain mappings
Wires list/create/update/set-active/delete onto the SiteDomainMappingService
through gin handlers.
2026-04-29 16:01:54 +08:00
root edf9f53999 feat(ops/site-mappings): add service layer for site domain mappings
Wraps the repository with input normalization (lowercase domain, host-
extraction from URLs, length limits) and audit logging for
create/update/enable/disable/delete actions, ready to be wired into the
ops console handlers.
2026-04-29 16:01:15 +08:00
root eafb157174 feat(ops/site-mappings): add repository CRUD for site_domain_mappings
Add SiteDomainMappingRepository with list/get/create/update/set-active/
delete to back the ops console management UI on top of the now-global
site_domain_mappings table.
2026-04-29 16:00:49 +08:00
root 674709c86b feat(monitoring/site-mappings): globalize site_domain_mappings
Drop tenant scoping on site_domain_mappings now that the citation query
resolves mappings without tenant filtering. Add a unique index on
registrable_domain plus an updated_at column, and switch the dev-seed
upsert to use ON CONFLICT (registrable_domain). Adds a SiteDomainMapping
type to ops/domain for the upcoming console management UI.
2026-04-29 16:00:13 +08:00
root 01b289ba0b fix(desktop/login): pass identifier alongside email and add phone to preview user
Aligns the desktop client with the server's phone-or-email login API so
sign-in works after the identifier-based auth switch.
2026-04-29 15:59:35 +08:00
root a5cef078f5 feat(ops-web/auth): add remember-me with session/local storage split
Persistent logins land in localStorage, ephemeral ones in sessionStorage,
and we recall the last username + remember preference on the login form.
Also propagate the indigo brand color through the antd ConfigProvider
theme token so the side nav and avatar match the redesigned login.
2026-04-29 15:59:23 +08:00
root 2d66e32938 refactor(monitoring/citations): consolidate domain mapping via CTE
Replace the per-row tenant+global LATERAL joins with a single CTE that
materializes filtered citations and resolves domain mappings once per
distinct host/domain/site_key triple. Reduces redundant scans of
site_domain_mappings on large run batches.
2026-04-29 15:59:12 +08:00
root 0ce20ac2fa fix(desktop/sohuhao): set article infoResource to 0 2026-04-29 15:59:00 +08:00
root 59ad14ef2c feat(desktop/account-health): treat active/expiring_soon probes as live and dedupe in-flight checks
Why: avoid showing accounts as not-live during stale-window probes; keep publish gating consistent with runtime view.

- collapse FIRST_PROBE window so initial probe runs immediately
- skip due-probe selection when a probe is already queued/in-flight
- mirror auth-state→health projection on both client report and tenant ingest paths

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 15:58:25 +08:00
root f8b918b9cd style(ops-web/login): redesign login with animated mascot and brand panel
Replace the previous split-screen with a brand panel that hosts an
animated mascot (eyeballs whose pupils track the focused form field and
hide while the password is typed) and decorative grid/circles, plus a
mobile brand mark and footer links. Swap Ant Design form controls for
native inputs styled to match. The mascot is composed from new
AnimatedCharacters / EyeBall / Pupil components.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 00:50:19 +08:00
root 31f1ef35b4 fix(ops-api/audit): guard ip2region searcher with per-version mutex
The buffer-backed xdb.Searcher is not safe for concurrent use, so
parallel audit appends could race on Search and corrupt internal state.
Serialize v4 and v6 lookups behind separate mutexes so concurrent
callers fall through one at a time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 00:50:10 +08:00
root 3480d04ec3 feat(ops-api/audit): annotate audit log IP with region lookup
Add an IPRegionResolver wrapping the ip2region xdb library and attach it
to AuditService so each appended event records both the raw IP and a
resolved region in a new ops.audit_logs.ip_region column. Loopback and
private addresses short-circuit to local labels; missing xdb data or
lookup errors degrade silently so auditing keeps working without it.
The ops console audit view shows the region beneath the IP. Bundle the
v4/v6 xdb data under internal/ops/app/ipregiondata so the resolver works
out of the box, with config paths/env overrides for swapping in updated
data sets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 00:46:37 +08:00
root 5a9d80c518 chore(server/membership): seed AI points for free and plus default plans
Free plan now ships with 10 AI points/month and Plus with 1000, replacing
the placeholder zeroes left over when the feature was first wired up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 00:02:55 +08:00
root 7be0de0614 feat(ops): add tenant admin user management module
Adds /admin-users CRUD on the ops backend, covering listing, plan/role/KOL
toggles, subscription expiry, status flips and password resets, with a
configurable default plan code. The ops console exposes a new top-level
"用户管理" view and reorganises the sidebar so 操作员管理 and 审计日志
move under a "系统设置" group; AccountsView is renamed accordingly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 00:02:46 +08:00
root 62b824520c feat(admin-web/auth): support phone-or-email login and dim shell on expiry
Login form takes a phone-or-email identifier; user info now exposes phone
so the shell can fall back through name → phone → email. When membership
expires, the app stays on the current route but renders a minimal blocked
state in the shell instead of redirecting to a dedicated page; an Axios
interceptor flips the local session into the blocked state when the API
returns trial/subscription errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 00:01:47 +08:00
root ee21f512a9 feat(server/auth): switch user identity to phone with email optional
Phone becomes the required, unique login identifier; email and name turn
optional. Login now accepts either via a single identifier field, refresh
re-checks tenant membership, and the dev seed provides phone numbers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 00:01:36 +08:00