- Implemented a new BrandAssetCleanupWorker to handle the cleanup of brand-related assets after a brand is deleted.
- Added SQL queries for cleaning up articles, keywords, questions, competitors, and monitoring data associated with a brand.
- Introduced a new API endpoint to delete publish records.
- Updated the router to include the new delete publish record endpoint.
- Added tests for the BrandAssetCleanupWorker to ensure proper functionality.
- Created migration scripts to support soft deletion of publish records and to add the brand asset cleanup scheduler job.
Add WordPress as an enterprise-site CMS type alongside pbootcms, and let
schedule tasks auto-publish generated articles to enterprise sites.
- WordPress connection/publisher service and tests; register wordpress
media platform and relax cms_type CHECK constraint
- New publish_enterprise_site_targets JSONB column on schedule_tasks with
array type constraint; thread targets through scheduler dispatch,
prompt/KOL generation, and worker
- Admin UI: enterprise-site targets in generate/schedule/publish flows,
KOL package form, MediaView, and i18n strings
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Support a random daily run window (random_window_start/end) where the
next auto-run time is derived from a stable per-job hash, avoiding
thundering-herd starts while keeping one run per day. Honors the last
auto-run on initial scheduling so restarts don't double-fire.
Manual triggers can opt into dedupe (dedupe_manual_triggers): an
advisory-locked path collapses concurrent requests onto an existing
pending/running job instead of queueing duplicates. Scheduler error
messages are localized to Chinese.
Register the media_supply_cache_sync job (dry-run aware) and add the
ops migration that seeds it.
The desktop publish queue could stall for a long time and end users assumed
the software was broken. Root cause: a hung adapter held its execution slot
with no wall-clock timeout while auto-renewing its lease forever, so the
server never reclaimed it and every queued task behind it stayed 等待发布.
Auto-recovery also risked silently re-posting a non-idempotent article.
Client (Electron):
- per-task wall-clock deadline + abort; progress-gated lease renewal that
stops and aborts a stalled task instead of renewing it forever
- decouple the concurrency cap from CDP-induced CPU/memory pressure
(admission gate instead of self-throttling collapse); 15s watchdog pump
- all adapter network I/O now has fetch timeouts and honors context.signal;
bounded image-upload concurrency with per-image timeout
- surface live adapter progress, elapsed time, queue position and a
working-vs-queued distinction in the publish view
Server (tenant-api):
- publish lease-recovery worker (every 3m) + supporting index: reclaim
expired in_progress publish leases, requeue (<3 attempts) or terminal-fail
- 3-minute lease TTL with client-presence-gated extension; max 3 attempts
Idempotency (production-grade core):
- durable publish_submit_started_at marker, set before the irreversible
platform submit POST; recovery and abort route a maybe-submitted task to
unknown (manual reconcile, kept in the dedup set) instead of re-posting
- desktop UI requires explicit confirmation before retrying a possibly-
already-published task
Verified: go build/vet/test (incl. resolvePublishRecoveryOutcome), vue-tsc,
vitest 141/141, gofmt all clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce an end-to-end media-supply feature: tenant-side resource sync
service/worker backed by a Meijiequan supplier client, ops-side management
APIs, and admin/ops web views for resources, orders, favorites and
submission. Adds a shared digitocr helper, MediaSupply config blocks for
tenant and ops, shared types, and migrations for supplier media resources,
price overrides, customer visibility and order refunds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ship the Ops backstage 对象存储 page (browse / upload / move / delete /
folder ops) backed by a new object-storage service + handler, with the
shared storage client extended with List/Stat/Copy/Usage/DownloadURL so
both MinIO and Aliyun providers cover the new surface. Add
ForcePathStyle to the object-storage config and treat r2/s3/custom_s3
as external providers so Cloudflare R2 and other S3-compatibles can
share the MinIO client path without spinning up the bundled MinIO.
Also add CSV import/export + template download to the site-domain
mappings page, raise gin MaxMultipartMemory to 8 MiB for the new
multipart endpoints, register Ops swagger routes, and extend the
descriptions-parity test to cover the ops router.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend schedule tasks to dispatch KOL subscription prompts in addition
to prompt rules, add daily/weekly scheduling with fixed or random time
windows, and track dispatch outcomes (last run, status, consecutive
failures).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the scheduler-polled KnowledgeDeletedCleanupWorker with an
event-driven KnowledgeDeletedCleanupEventWorker in tenant-api; cleanup
events are now enqueued transactionally at delete time instead of swept
periodically. Also fixes statement_timeout parameterization in
MonitoringRetentionWorker and propagates errors from cleanupDeletedKnowledgeItem.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- add /questions/combination-fill endpoint with AI-driven, IP-region-aware matrix fill
- extract ip2region resolver from ops/app into shared/ipregion for cross-service use
- thread question_id filter through dashboard composite, citation summary, and collect-now
- switch template wizard from keyword inputs to brand-question selection (primary + supplemental)
- pass brand_question and supplemental_questions through assist/title/outline prompts
- add AiWaitingModal + 45s client timeout and request_timeout error mapping for long AI flows
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When ops staff reset a tenant's current-plan usage, the existing
'refunded' terminal status conflated user-initiated refunds with
ops-driven resets, and tenants still saw the old balance in the top-nav
chip until the quota-summary cache TTL expired.
- Introduce a dedicated 'reset' status for quota_reservations, separate
from 'refunded'. Both the initial and an incremental migration widen
the CHECK constraint to allow it; the down migration folds 'reset'
rows back into 'refunded' before tightening the constraint.
- resetQuotaReservations / resetAIPointReservations now write 'reset'
without touching refunded_amount, and stop cascading into
ai_point_usage_logs (carried no audit value for ops resets).
- AdminUserService gains an optional cache dependency and calls
invalidateQuotaSummaryCache(tenant_id) immediately after a successful
reset so the tenant's top-nav chip refreshes on the next request
without waiting on TTL.
- Wire the existing appCache into AdminUserService at boot.
- Drop the now-unused ai_point_usage_logs field from the reset response
and from the ops-web AdminUsersView type.
- Add a unit test covering the cache-key invalidation contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The state-consistency worker now detects articles still flagged as
generating whose latest generation_task is already failed or completed
(with a version) and aligns the article status to the terminal task,
invalidating article caches on the way out. Adds supporting partial
indexes and switches UpdateGenerationTaskStatus to COALESCE started_at
and completed_at so partial status updates don't wipe the timestamps.
Adds a new browser-fetch microservice that wraps Lightpanda for rendering
JS-heavy pages, and wires it into the knowledge URL parser as a fallback
when the upstream link reader returns 403/429/5xx for an allow-listed
domain (e.g. zhuanlan.zhihu.com). Includes config/env plumbing, hot-reload
diff support, and full deploy assets (Dockerfile target, docker-compose,
k3s manifests, image-build/package scripts).
Provide a unified ops console for inspecting, retrying and cancelling
jobs across generation, template/kol assist, knowledge parse, desktop
publish/task, compliance review and monitoring collect sources. Wires
RabbitMQ for retry republish and consolidates the desktop_publish_jobs
columns into the base migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- shared/cache: add Options/L1/async/metrics/prefix decorators, multi-key ops, Redis pool tuning, and JSON readthrough metrics
- worker-generate: claim tasks via DB lease + heartbeat, requeue stale queued tasks, expire dead leases with refund/cache invalidation
- tenant: version article cache keys so worker recovery invalidations propagate cleanly
- shared/config: expand Redis (pool/timeouts/TLS) and Generation (lease/recovery) configs with defaults
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Translate all login error messages to Chinese (network errors, HTTP
status codes, URL validation errors)
- Add password visibility toggle (eye icon) to login form
- Add live countdown timer when login is locked, disable login button
during cooldown, auto-clear error when countdown ends
- Add X button red hover feedback in server settings dialog
- Add LoginGuard.Unlock() method to clear Redis lock/failure keys
- Expose POST /admin-users/:id/reset-login-lock endpoint in ops API
- Add "重置登录保护" button in ops-web user management table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A fresh production environment only ran migrations, so the four built-in
platform templates were never inserted unless someone manually ran
dev-seed (which also writes broad demo data). Extract the platform
template upsert into a reusable repository.EnsurePlatformTemplates and
add a standalone cmd/seed-platform-templates that calls it. Bake the
binary into the migrate image and chain it after migrate up in both
docker-compose and the k3s migration job (mounting app config so the
binary can dial the right database).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace single Load() with a watching Store that re-reads config files
(and config.local.yaml) and fans out a ReloadEvent with a per-field diff
so consumers can decide whether the change is hot-applicable or requires
a process restart.
- Wrap llm, retrieval, vector store, and object storage clients in
Reloadable* shells so the bootstrap can swap their underlying impls when
config changes without re-instantiating handlers.
- Make jwt.Manager and ops TokenIssuer mutable under a lock so secrets and
TTLs can be rotated live; thread default plan code through a setter on
the ops AdminUserService.
- Wire ConfigStore through bootstrap and every cmd/main.go, scheduler /
worker / tenant-api / ops-api start the watcher; services and handlers
take a config.Provider so they always read current values for things
like generation.stream_enabled, scheduler dispatch, retrieval, etc.
- Switch shared/config decoding off viper to a Kratos-derived runtime
package so env placeholders (\${VAR:default}) resolve consistently and
the same source machinery powers both the loader and the watcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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.
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>
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>
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>
Adds an ai_point_usage_logs ledger and a reserve/refund/complete pipeline
that charges AI points for article selection optimize, template analyze
/title/outline, and KOL prompt generate/optimize. Pending reservations
are reconciled when the kol-assist worker and template-assist tasks
finish or fail, so points refund automatically on errors. Workspace now
exposes the AI quota status and a paginated usage ledger.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stand up an internal ops-api service with operator account, auth, and
audit log subsystems backed by a dedicated migrations_ops migration set.
Wire Makefile targets and the migrate Dockerfile stage so the new schema
ships alongside the existing tenant + monitoring databases.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Daily monitoring tasks were pinned to a single primary client. If that
client went offline the task wedged even when another desktop client of
the same workspace was online and bound to the same account. Drop the
primary-client constraint on the materialized collect rows, and instead
pick a target per-platform from live account/client presence in Redis,
falling back to the DB client_id when the desktop client is still flagged
online. Desktop task lease for kind=monitor now matches by account
ownership in addition to target_client_id, so any client owning the
account can drain the queue.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a POST /desktop/accounts/health-reports endpoint that buffers reports in
Redis, dedupes via signature, and publishes change events to a new
desktop.account.health RabbitMQ queue. A sink worker drains the queue, and
the account service overlays runtime health onto database health when serving
desktop accounts. Publish job creation now consults runtime health so a stale
DB row no longer blocks publishing once the desktop client reports it healthy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce 仿写创作: new list and create views, backend imitation service
and prompt templates, worker task routing for imitation jobs, and one-click
rewrite triggers from question citation sources. Surface source article
URL/title on article list/detail, and restrict failed articles to delete-only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scheduler now runs a token-protected HTTP server on scheduler.http_port
(default 8081, -1 disables) that exposes Prometheus /metrics and the
daily-task JSON snapshot endpoint. Workers gain a blocking Run(ctx) and
are supervised by a WaitGroup: on SIGINT/SIGTERM the metrics server
shuts down first, worker ticks stop scheduling but ongoing runOnce
calls keep their own context and finish, and the process waits up to
60s before exiting. Adds scheduler.http_host/http_port/internal_metrics_token
to config with SCHEDULER_* env overrides and exposes port 8081 in the
Docker image.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>