Compare commits
No commits in common. "main" and "feat/add_search" have entirely different histories.
main
...
feat/add_s
|
|
@ -1,15 +1,9 @@
|
||||||
.git
|
|
||||||
.run
|
.run
|
||||||
.cursor
|
.git
|
||||||
**/node_modules
|
web/node_modules
|
||||||
**/.env
|
# web/dist 保留給 deploy/Dockerfile.web.static(本機 make web-build 後 COPY)
|
||||||
backend/bin
|
worker/node_modules
|
||||||
backend/web/dist
|
|
||||||
backend/web/public/downloads/*.zip
|
|
||||||
backend/dev-console/dist
|
|
||||||
frontend/dist
|
|
||||||
**/*_test.go
|
**/*_test.go
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
infra
|
|
||||||
*.md
|
*.md
|
||||||
!deploy/**
|
!deploy/**
|
||||||
|
|
@ -1,29 +1,15 @@
|
||||||
# --- secrets / env ---
|
# --- secrets / env(不要 commit 實值)---
|
||||||
deploy/.env
|
|
||||||
deploy/.env.dev
|
|
||||||
deploy/.env.prod
|
|
||||||
infra/.env
|
infra/.env
|
||||||
*.env
|
infra/etc/haixun.env
|
||||||
**/.env
|
/opt/haixun/
|
||||||
!.env.example
|
|
||||||
!*.env.example
|
|
||||||
|
|
||||||
# --- runtime / logs ---
|
# --- build 產物 ---
|
||||||
.run/
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# --- build artifacts ---
|
|
||||||
backend/bin/
|
backend/bin/
|
||||||
backend/web/dist/
|
|
||||||
backend/dev-console/dist/
|
|
||||||
frontend/dist/
|
frontend/dist/
|
||||||
dist/
|
|
||||||
backend/web/public/downloads/*.zip
|
|
||||||
|
|
||||||
# --- dependencies ---
|
# --- 依賴 ---
|
||||||
node_modules/
|
node_modules/
|
||||||
**/node_modules/
|
|
||||||
|
|
||||||
# --- OS / editor ---
|
# --- 其他 ---
|
||||||
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.cursor/
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
72708
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
> haixun-web@0.1.0 dev
|
||||||
|
> vite
|
||||||
|
|
||||||
|
|
||||||
|
VITE v6.4.3 ready in 187 ms
|
||||||
|
|
||||||
|
➜ Local: http://localhost:5173/
|
||||||
|
➜ Network: use --host to expose
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1 @@
|
||||||
|
72713
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
72714
|
||||||
|
|
@ -22,7 +22,6 @@
|
||||||
## 設計文件
|
## 設計文件
|
||||||
|
|
||||||
- `docs/job-system-plan.md`:通用 job system 規劃,包含 template、run、schedule、Redis queue/lock、取消語意與 API 草案。
|
- `docs/job-system-plan.md`:通用 job system 規劃,包含 template、run、schedule、Redis queue/lock、取消語意與 API 草案。
|
||||||
- `docs/post-project-plan.md`:發文計畫(文案任務 Flow A 砍掉重做)— 主題發想、風格分析、矩陣產 N 篇草稿、定時發送。
|
|
||||||
- `docs/scan-placement-plan.md`:海巡獲客(流程 B)— 知識圖譜、Brave 擴展、雙軌爬取(7 天重點 / 30 天補充)、產品匹配、島民交接。
|
- `docs/scan-placement-plan.md`:海巡獲客(流程 B)— 知識圖譜、Brave 擴展、雙軌爬取(7 天重點 / 30 天補充)、產品匹配、島民交接。
|
||||||
|
|
||||||
## 新增 API 流程
|
## 新增 API 流程
|
||||||
|
|
|
||||||
307
Makefile
307
Makefile
|
|
@ -1,128 +1,229 @@
|
||||||
|
# 巡樓 monorepo Makefile
|
||||||
|
# 兩種模式:
|
||||||
|
# dev - 本機開發(docker 起 Mongo/Redis,go run / vite dev)
|
||||||
|
# prod - 建置產物(前端 dist + linux Go binary),並可部署成 systemd 服務 + nginx
|
||||||
|
#
|
||||||
|
# 常用:
|
||||||
|
# make dev-infra # 起本機 Mongo/Redis
|
||||||
|
# make dev-backend # 跑 gateway (:8890)
|
||||||
|
# make dev-frontend # 跑前端 (:5173,proxy 到 :8890)
|
||||||
|
# make build # 產出 frontend/dist + backend/bin/{gateway,worker}
|
||||||
|
# sudo make install # 部署到目標主機(見 infra/README.md)
|
||||||
|
|
||||||
SHELL := /bin/bash
|
SHELL := /bin/bash
|
||||||
|
|
||||||
BACKEND_DIR := backend
|
# --- 路徑 ---
|
||||||
WEB_DIR := backend/web
|
BACKEND_DIR := backend
|
||||||
INFRA_DIR := infra
|
FRONTEND_DIR := frontend
|
||||||
DEPLOY_DIR := deploy
|
INFRA_DIR := infra
|
||||||
|
BIN_DIR := $(BACKEND_DIR)/bin
|
||||||
|
|
||||||
INFRA_DEV_COMPOSE := docker compose --env-file $(DEPLOY_DIR)/.env.dev -f $(INFRA_DIR)/docker-compose.yml
|
# --- 部署目標(install 用)---
|
||||||
INFRA_PROD_COMPOSE := docker compose --env-file $(DEPLOY_DIR)/.env.prod -f $(INFRA_DIR)/docker-compose.yml
|
DEPLOY_ROOT := /opt/haixun
|
||||||
DEPLOY_COMPOSE := docker compose --env-file $(DEPLOY_DIR)/.env.prod -f $(DEPLOY_DIR)/docker-compose.yml
|
WEB_ROOT := /var/www/haixun
|
||||||
|
|
||||||
|
# --- 交叉編譯目標(在 mac 上 build 給 linux 主機)---
|
||||||
|
GOOS ?= linux
|
||||||
|
GOARCH ?= amd64
|
||||||
|
|
||||||
|
# --- docker compose ---
|
||||||
|
COMPOSE := docker compose -f $(INFRA_DIR)/docker-compose.yml --env-file $(INFRA_DIR)/.env
|
||||||
|
|
||||||
|
# --- dev 用 worker secret(對應 etc/gateway.yaml 的 InternalWorker.Secret)---
|
||||||
|
DEV_WORKER_SECRET := haixun-dev-worker-secret
|
||||||
|
DEV_BACKEND_URL := http://127.0.0.1:8890
|
||||||
|
|
||||||
.DEFAULT_GOAL := help
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help: ## 顯示可用指令
|
help: ## 顯示可用指令
|
||||||
@grep -E '^[a-zA-Z0-9_-]+:.*## .*$$' $(MAKEFILE_LIST) \
|
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
|
||||||
| awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
|
| awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
.PHONY: setup-env
|
# ============================================================
|
||||||
setup-env: ## 互動式產生 deploy/.env.prod(自動 URL-encode MongoDB 密碼)
|
# DEV
|
||||||
@$(DEPLOY_DIR)/setup.sh
|
# ============================================================
|
||||||
|
|
||||||
.PHONY: gen-api
|
$(INFRA_DIR)/.env:
|
||||||
gen-api: ## 重新產生後端 API handler / logic / types
|
@test -f $(INFRA_DIR)/.env || (cp $(INFRA_DIR)/.env.example $(INFRA_DIR)/.env && echo "已從 .env.example 建立 $(INFRA_DIR)/.env,請視需要修改密碼")
|
||||||
$(MAKE) -C $(BACKEND_DIR) gen-api
|
|
||||||
|
|
||||||
.PHONY: fmt
|
|
||||||
fmt: ## 格式化後端 Go 程式
|
|
||||||
$(MAKE) -C $(BACKEND_DIR) fmt
|
|
||||||
|
|
||||||
.PHONY: test
|
|
||||||
test: ## 執行後端測試
|
|
||||||
$(MAKE) -C $(BACKEND_DIR) test
|
|
||||||
|
|
||||||
.PHONY: init
|
|
||||||
init: ## 初始化系統(建索引、種權限、建立 admin,讀 deploy/.env.prod)
|
|
||||||
$(MAKE) -C $(BACKEND_DIR) ENV_FILE=../deploy/.env.prod init
|
|
||||||
|
|
||||||
.PHONY: run
|
|
||||||
run: ## 啟動後端 API
|
|
||||||
$(MAKE) -C $(BACKEND_DIR) run
|
|
||||||
|
|
||||||
.PHONY: dev-all
|
|
||||||
dev-all: ## 啟動本機完整 dev stack(API、Go workers、Node workers、web)
|
|
||||||
./scripts/dev-all.sh
|
|
||||||
|
|
||||||
.PHONY: dev-all-stop
|
|
||||||
dev-all-stop: ## 停止本機 dev stack(釋放 8890 / 9101 / 9102 / web 等埠)
|
|
||||||
./scripts/dev-all-stop.sh
|
|
||||||
|
|
||||||
.PHONY: web-dev
|
|
||||||
web-dev: ## 啟動正式前端 dev server
|
|
||||||
cd $(WEB_DIR) && npm install && npm run dev
|
|
||||||
|
|
||||||
.PHONY: web-build
|
|
||||||
web-build: ## 建置正式前端
|
|
||||||
cd $(WEB_DIR) && npm install && npm run build
|
|
||||||
|
|
||||||
.PHONY: verify
|
|
||||||
verify: test web-build ## 後端測試與前端建置
|
|
||||||
|
|
||||||
.PHONY: dev-infra
|
.PHONY: dev-infra
|
||||||
dev-infra: ## 啟動本機 dev 資料服務(Mongo / Redis 容器,讀 deploy/.env.dev)
|
dev-infra: $(INFRA_DIR)/.env ## [dev] 起本機 Mongo + Redis (docker)
|
||||||
@test -f $(DEPLOY_DIR)/.env.dev || cp $(DEPLOY_DIR)/.env.dev.example $(DEPLOY_DIR)/.env.dev
|
$(COMPOSE) up -d
|
||||||
@echo ">>> 請先編輯 $(DEPLOY_DIR)/.env.dev 再執行後續指令" >&2
|
$(COMPOSE) ps
|
||||||
$(INFRA_DEV_COMPOSE) up -d
|
|
||||||
$(INFRA_DEV_COMPOSE) ps
|
|
||||||
|
|
||||||
.PHONY: dev-infra-down
|
.PHONY: dev-infra-down
|
||||||
dev-infra-down: ## 關閉本機 dev 資料服務(保留資料 volume)
|
dev-infra-down: ## [dev] 停掉本機 Mongo + Redis
|
||||||
$(INFRA_DEV_COMPOSE) down
|
$(COMPOSE) down
|
||||||
|
|
||||||
.PHONY: dev-infra-recreate
|
.PHONY: dev-backend
|
||||||
dev-infra-recreate: ## 清除 dev 資料 volume 並重啟(Mongo / Redis 全新狀態)
|
dev-backend: ## [dev] 跑 gateway API (:8890)
|
||||||
$(INFRA_DEV_COMPOSE) down -v
|
cd $(BACKEND_DIR) && go run . -f etc/gateway.yaml
|
||||||
$(INFRA_DEV_COMPOSE) up -d
|
|
||||||
$(INFRA_DEV_COMPOSE) ps
|
.PHONY: dev-worker
|
||||||
|
dev-worker: ## [dev] 跑 Go job worker (:8891)
|
||||||
|
cd $(BACKEND_DIR) && go run ./cmd/worker -f etc/gateway.worker.yaml
|
||||||
|
|
||||||
|
.PHONY: dev-node-worker
|
||||||
|
dev-node-worker: ## [dev] 跑 Node playwright worker (style-8d)
|
||||||
|
cd $(BACKEND_DIR)/worker && npm install && \
|
||||||
|
HAIXUN_WORKER_SECRET=$(DEV_WORKER_SECRET) HAIXUN_BACKEND_URL=$(DEV_BACKEND_URL) npm run style-8d
|
||||||
|
|
||||||
|
.PHONY: dev-frontend
|
||||||
|
dev-frontend: ## [dev] 跑前端 dev server (:5173)
|
||||||
|
cd $(FRONTEND_DIR) && npm install && npm run dev
|
||||||
|
|
||||||
|
.PHONY: dev-init
|
||||||
|
dev-init: ## [dev] 初始化 DB(索引/權限)並建立 admin 帳號(可用 INIT_ADMIN_EMAIL / INIT_ADMIN_PASSWORD 覆寫)
|
||||||
|
cd $(BACKEND_DIR) && go run ./cmd/tool init -f etc/gateway.yaml
|
||||||
|
|
||||||
|
.PHONY: dev
|
||||||
|
dev: ## [dev] 顯示本機開發要開的終端
|
||||||
|
@echo "本機開發請分開幾個終端執行:"
|
||||||
|
@echo " 1) make dev-infra # Mongo/Redis"
|
||||||
|
@echo " 2) make dev-backend # gateway :8890"
|
||||||
|
@echo " 3) make dev-worker # go worker(可選)"
|
||||||
|
@echo " 4) make dev-node-worker # node worker(需 style-8d 時)"
|
||||||
|
@echo " 5) make dev-frontend # 前端 :5173"
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 安裝依賴(新機器 clone 後執行一次)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
.PHONY: bootstrap-sys
|
||||||
|
bootstrap-sys: ## [需 sudo] 安裝系統依賴(Go, Node.js, Docker, Nginx)— Ubuntu 專用
|
||||||
|
@echo "=== 安裝系統套件: nginx, curl, gnupg ==="
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y -qq nginx curl gnupg ca-certificates
|
||||||
|
@echo ""
|
||||||
|
@echo "=== 安裝 Go 1.22+ ==="
|
||||||
|
@GO_VER=$$(curl -sL https://go.dev/VERSION?m=text 2>/dev/null | head -1 || echo "go1.22"); \
|
||||||
|
echo "下載 $$GO_VER.linux-amd64.tar.gz"; \
|
||||||
|
curl -sL "https://go.dev/dl/$$GO_VER.linux-amd64.tar.gz" -o /tmp/go.tar.gz && \
|
||||||
|
sudo rm -rf /usr/local/go && \
|
||||||
|
sudo tar -C /usr/local -xzf /tmp/go.tar.gz && \
|
||||||
|
rm /tmp/go.tar.gz; \
|
||||||
|
echo 'export PATH=/usr/local/go/bin:$$PATH' | sudo tee /etc/profile.d/go.sh
|
||||||
|
@echo ""
|
||||||
|
@echo "=== 安裝 Node.js LTS ==="
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
||||||
|
sudo apt-get install -y -qq nodejs
|
||||||
|
@echo ""
|
||||||
|
@echo "=== 安裝 Docker Engine + Compose ==="
|
||||||
|
curl -fsSL https://get.docker.com | sudo sh
|
||||||
|
sudo usermod -aG docker $(USER) 2>/dev/null || true
|
||||||
|
@echo ""
|
||||||
|
@echo "=== 安裝 Nginx ==="
|
||||||
|
sudo apt-get install -y -qq nginx
|
||||||
|
@echo ""
|
||||||
|
@echo "✓ 系統依賴安裝完成。"
|
||||||
|
@echo " 登出再登入後,Go/Node/Docker 即可使用(group 變更生效)。"
|
||||||
|
@echo " 接著執行 make bootstrap 安裝專案層依賴。"
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
.PHONY: bootstrap
|
||||||
|
bootstrap: ## 安裝專案層依賴(Go modules + npm + Playwright),需先執行 make bootstrap-sys
|
||||||
|
@echo "=== [1/4] Go modules ==="
|
||||||
|
cd $(BACKEND_DIR) && go mod tidy
|
||||||
|
@echo ""
|
||||||
|
@echo "=== [2/4] 前端 npm ==="
|
||||||
|
cd $(FRONTEND_DIR) && npm install
|
||||||
|
@echo ""
|
||||||
|
@echo "=== [3/4] Node worker npm ==="
|
||||||
|
cd $(BACKEND_DIR)/worker && npm install
|
||||||
|
@echo ""
|
||||||
|
@echo "=== [4/4] Playwright 瀏覽器(chromium)+ 系統依賴 ==="
|
||||||
|
cd $(BACKEND_DIR)/worker && sudo npx playwright install --with-deps chromium
|
||||||
|
@echo ""
|
||||||
|
@echo "✓ 全裝完成。後續步驟:"
|
||||||
|
@echo " 1) make dev-infra # 起 Mongo/Redis (Docker)"
|
||||||
|
@echo " 2) make dev-init # 初始化 DB + 建立 admin"
|
||||||
|
@echo " 3) make dev-backend # 啟動 gateway (:8890)"
|
||||||
|
@echo " 4) sudo make install # 正式部署(含 systemd + nginx)"
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# BUILD (prod 產物)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build: build-frontend build-backend ## [prod] 建置前端 dist 與 Go binary
|
||||||
|
|
||||||
|
.PHONY: build-frontend
|
||||||
|
build-frontend: ## [prod] 前端靜態建置 (tsc + vite) -> frontend/dist
|
||||||
|
cd $(FRONTEND_DIR) && npm ci && npm run build
|
||||||
|
|
||||||
|
.PHONY: build-backend
|
||||||
|
build-backend: ## [prod] 交叉編譯 gateway + worker -> backend/bin (linux)
|
||||||
|
cd $(BACKEND_DIR) && CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) \
|
||||||
|
go build -trimpath -ldflags "-s -w" -o bin/gateway .
|
||||||
|
cd $(BACKEND_DIR) && CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) \
|
||||||
|
go build -trimpath -ldflags "-s -w" -o bin/worker ./cmd/worker
|
||||||
|
cd $(BACKEND_DIR) && CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) \
|
||||||
|
go build -trimpath -ldflags "-s -w" -o bin/tool ./cmd/tool
|
||||||
|
@echo "binary 已輸出到 $(BIN_DIR)/(gateway / worker / tool)"
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# PROD (部署)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
.PHONY: prod-infra
|
.PHONY: prod-infra
|
||||||
prod-infra: ## 啟動 prod 資料服務(Mongo / Redis,讀 deploy/.env.prod)
|
prod-infra: $(INFRA_DIR)/.env ## [prod] 起 Mongo + Redis (docker,背景)
|
||||||
@test -f $(DEPLOY_DIR)/.env.prod || cp $(DEPLOY_DIR)/.env.prod.example $(DEPLOY_DIR)/.env.prod
|
$(COMPOSE) up -d
|
||||||
@echo ">>> 請先編輯 $(DEPLOY_DIR)/.env.prod 再執行後續指令" >&2
|
|
||||||
$(INFRA_PROD_COMPOSE) up -d
|
|
||||||
$(INFRA_PROD_COMPOSE) ps
|
|
||||||
|
|
||||||
.PHONY: prod-infra-down
|
.PHONY: prod-infra-down
|
||||||
prod-infra-down: ## 關閉 prod 資料服務(保留資料 volume)
|
prod-infra-down: ## [prod] 停掉 Mongo + Redis
|
||||||
$(INFRA_PROD_COMPOSE) down
|
$(COMPOSE) down
|
||||||
|
|
||||||
.PHONY: prod-infra-recreate
|
.PHONY: prod-init
|
||||||
prod-infra-recreate: ## 清除 prod 資料 volume 並重啟(Mongo / Redis 全新狀態)
|
prod-init: ## [prod] 目標主機初始化 DB + 建立 admin(讀 /opt/haixun/etc/haixun.env;可加 INIT_ADMIN_EMAIL/PASSWORD)
|
||||||
$(INFRA_PROD_COMPOSE) down -v
|
@test -x $(DEPLOY_ROOT)/bin/tool || (echo "缺少 $(DEPLOY_ROOT)/bin/tool,請先 make install" && exit 1)
|
||||||
$(INFRA_PROD_COMPOSE) up -d
|
set -a; . $(DEPLOY_ROOT)/etc/haixun.env; set +a; \
|
||||||
$(INFRA_PROD_COMPOSE) ps
|
$(DEPLOY_ROOT)/bin/tool init -f $(DEPLOY_ROOT)/etc/gateway.prod.yaml
|
||||||
|
|
||||||
.PHONY: prod-install
|
.PHONY: install
|
||||||
prod-install: ## 一鍵部署 prod:複製 .env.prod、build 本地 image、啟動 app、init(需先啟動 infra)
|
install: ## [prod] 安裝 binary/前端/設定/systemd/nginx(需 root,在目標主機執行)
|
||||||
@test -f $(DEPLOY_DIR)/.env.prod || cp $(DEPLOY_DIR)/.env.prod.example $(DEPLOY_DIR)/.env.prod
|
@test -f $(BIN_DIR)/gateway || (echo "缺少 $(BIN_DIR)/gateway,請先在能 build 的機器執行 make build" && exit 1)
|
||||||
@echo ">>> 請先編輯 $(DEPLOY_DIR)/.env.prod 再執行後續指令" >&2
|
@test -d $(FRONTEND_DIR)/dist || (echo "缺少 $(FRONTEND_DIR)/dist,請先 make build-frontend" && exit 1)
|
||||||
@echo ">>> 請確認 infra 已啟動(make prod-infra)" >&2
|
id haixun >/dev/null 2>&1 || useradd --system --home $(DEPLOY_ROOT) --shell /usr/sbin/nologin haixun
|
||||||
$(DEPLOY_COMPOSE) build
|
install -d $(DEPLOY_ROOT)/bin $(DEPLOY_ROOT)/etc $(DEPLOY_ROOT)/node-worker $(WEB_ROOT)
|
||||||
$(DEPLOY_COMPOSE) up -d
|
install -m 0755 $(BIN_DIR)/gateway $(DEPLOY_ROOT)/bin/gateway
|
||||||
$(DEPLOY_COMPOSE) --profile init run --rm init
|
install -m 0755 $(BIN_DIR)/worker $(DEPLOY_ROOT)/bin/worker
|
||||||
$(DEPLOY_COMPOSE) ps
|
install -m 0755 $(BIN_DIR)/tool $(DEPLOY_ROOT)/bin/tool
|
||||||
|
install -m 0644 $(BACKEND_DIR)/etc/gateway.prod.yaml $(DEPLOY_ROOT)/etc/gateway.prod.yaml
|
||||||
|
install -m 0644 $(BACKEND_DIR)/etc/gateway.worker.prod.yaml $(DEPLOY_ROOT)/etc/gateway.worker.prod.yaml
|
||||||
|
rm -rf $(WEB_ROOT)/* && cp -r $(FRONTEND_DIR)/dist/* $(WEB_ROOT)/
|
||||||
|
cp -r $(BACKEND_DIR)/worker/* $(DEPLOY_ROOT)/node-worker/
|
||||||
|
cd $(DEPLOY_ROOT)/node-worker && npm ci && npx playwright install --with-deps chromium
|
||||||
|
install -m 0644 $(INFRA_DIR)/systemd/haixun-gateway.service /etc/systemd/system/haixun-gateway.service
|
||||||
|
install -m 0644 $(INFRA_DIR)/systemd/haixun-worker.service /etc/systemd/system/haixun-worker.service
|
||||||
|
install -m 0644 $(INFRA_DIR)/systemd/haixun-node-worker.service /etc/systemd/system/haixun-node-worker.service
|
||||||
|
install -m 0644 $(INFRA_DIR)/nginx/haixun.conf /etc/nginx/conf.d/haixun.conf
|
||||||
|
chown -R haixun:haixun $(DEPLOY_ROOT) $(WEB_ROOT)
|
||||||
|
@echo "----"
|
||||||
|
@echo "接著(只做一次)建立 secret 檔:"
|
||||||
|
@echo " cp $(INFRA_DIR)/etc/haixun.env.example $(DEPLOY_ROOT)/etc/haixun.env && chmod 600 $(DEPLOY_ROOT)/etc/haixun.env && sudoedit $(DEPLOY_ROOT)/etc/haixun.env"
|
||||||
|
@echo "再啟用服務:"
|
||||||
|
@echo " systemctl daemon-reload && systemctl enable --now haixun-gateway haixun-worker haixun-node-worker"
|
||||||
|
@echo " nginx -t && systemctl reload nginx"
|
||||||
|
|
||||||
.PHONY: prod-rebuild
|
# ============================================================
|
||||||
prod-rebuild: ## 重新 build 本地 image 並重建 prod app stack(infra 不影響)
|
# 驗證 / 維護
|
||||||
$(DEPLOY_COMPOSE) build
|
# ============================================================
|
||||||
$(DEPLOY_COMPOSE) up -d
|
|
||||||
$(DEPLOY_COMPOSE) ps
|
|
||||||
|
|
||||||
.PHONY: prod-stop
|
.PHONY: tidy
|
||||||
prod-stop: ## 關閉 prod app stack(保留 infra 與資料)
|
tidy: ## go mod tidy
|
||||||
$(DEPLOY_COMPOSE) down
|
cd $(BACKEND_DIR) && go mod tidy
|
||||||
|
|
||||||
.PHONY: prod-stop-all
|
.PHONY: fmt
|
||||||
prod-stop-all: ## 關閉 prod app + infra(保留資料 volume)
|
fmt: ## gofmt 後端
|
||||||
$(DEPLOY_COMPOSE) down
|
cd $(BACKEND_DIR) && gofmt -w .
|
||||||
$(INFRA_PROD_COMPOSE) down
|
|
||||||
|
|
||||||
.PHONY: prod-recreate
|
.PHONY: test
|
||||||
prod-recreate: ## 完整清除 prod 全部資料 volume + 重建啟動(app + infra + init)
|
test: ## 跑後端測試
|
||||||
$(DEPLOY_COMPOSE) down -v
|
cd $(BACKEND_DIR) && go test ./...
|
||||||
$(INFRA_PROD_COMPOSE) down -v
|
|
||||||
$(INFRA_PROD_COMPOSE) up -d
|
.PHONY: verify
|
||||||
$(DEPLOY_COMPOSE) up -d
|
verify: ## 後端 build/test + 前端 build + compose 語法
|
||||||
$(DEPLOY_COMPOSE) --profile init run --rm init
|
cd $(BACKEND_DIR) && go build ./... && go test ./...
|
||||||
$(DEPLOY_COMPOSE) ps
|
cd $(FRONTEND_DIR) && npm ci && npm run build
|
||||||
|
$(COMPOSE) config >/dev/null && echo "docker compose config OK"
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
|
@ -1,24 +0,0 @@
|
||||||
FROM golang:1.24-alpine AS build
|
|
||||||
|
|
||||||
WORKDIR /src
|
|
||||||
RUN apk add --no-cache ca-certificates git
|
|
||||||
COPY backend/go.mod backend/go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
COPY backend/ ./
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/gateway ./gateway.go \
|
|
||||||
&& CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/worker ./cmd/worker \
|
|
||||||
&& CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/tool ./cmd/tool
|
|
||||||
|
|
||||||
FROM alpine:3.20
|
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates tzdata \
|
|
||||||
&& addgroup -S haixun \
|
|
||||||
&& adduser -S -G haixun -h /app haixun
|
|
||||||
WORKDIR /app
|
|
||||||
COPY --from=build /out/gateway /app/gateway
|
|
||||||
COPY --from=build /out/worker /app/worker
|
|
||||||
COPY --from=build /out/tool /app/tool
|
|
||||||
COPY backend/etc /app/etc
|
|
||||||
USER haixun
|
|
||||||
EXPOSE 8890
|
|
||||||
CMD ["/app/gateway", "-f", "/app/etc/gateway.prod.yaml"]
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
.PHONY: gen-api fmt test run web-dev web-build
|
|
||||||
|
|
||||||
API_FILE := generate/api/gateway.api
|
|
||||||
GOCTL_HOME := generate/goctl
|
|
||||||
GOCTL ?= $(shell go env GOPATH)/bin/goctl
|
|
||||||
|
|
||||||
ENV_FILE ?= ../deploy/.env.dev
|
|
||||||
|
|
||||||
ifneq ($(wildcard $(ENV_FILE)),)
|
|
||||||
# 把 deploy/.env.dev 的值 export 出來(給 make run / native binary 用;dev-all.sh 自行 source 故不走這條)
|
|
||||||
include $(ENV_FILE)
|
|
||||||
export HAIXUN_MONGO_URI HAIXUN_MONGO_DB HAIXUN_REDIS_ADDR HAIXUN_REDIS_PASSWORD
|
|
||||||
export HAIXUN_JWT_ACCESS_SECRET HAIXUN_JWT_REFRESH_SECRET HAIXUN_WORKER_SECRET
|
|
||||||
export HAIXUN_SECRETS_KEY HAIXUN_BACKEND_URL
|
|
||||||
export HAIXUN_WORKER_POLL_MS HAIXUN_8D_TARGET_SAMPLES PLAYWRIGHT_HEADLESS
|
|
||||||
export INIT_TENANT_ID INIT_ADMIN_EMAIL INIT_ADMIN_PASSWORD INIT_ADMIN_DISPLAY_NAME
|
|
||||||
endif
|
|
||||||
|
|
||||||
export HAIXUN_JOB_WORKER_TYPE ?= go-demo
|
|
||||||
export HAIXUN_JOB_WORKER_ID ?= local-go-demo
|
|
||||||
|
|
||||||
# 修改 generate/api/*.api 後執行,重新產生 routes / types / handler(logic 已存在則保留實作)
|
|
||||||
gen-api:
|
|
||||||
$(GOCTL) api go -api $(API_FILE) -dir . -style go_zero -home $(GOCTL_HOME)
|
|
||||||
|
|
||||||
fmt:
|
|
||||||
go fmt ./...
|
|
||||||
|
|
||||||
test:
|
|
||||||
go test ./...
|
|
||||||
|
|
||||||
run:
|
|
||||||
mkdir -p bin
|
|
||||||
go build -o bin/gateway gateway.go
|
|
||||||
./bin/gateway -f etc/gateway.yaml
|
|
||||||
|
|
||||||
run-dev:
|
|
||||||
go run gateway.go -f etc/gateway.yaml
|
|
||||||
|
|
||||||
init:
|
|
||||||
go run ./cmd/tool/main.go init -f etc/gateway.yaml
|
|
||||||
|
|
||||||
web-dev:
|
|
||||||
cd web && npm install && npm run dev
|
|
||||||
|
|
||||||
web-build:
|
|
||||||
cd web && npm install && npm run build
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
- `auth`:native email/password 登入、JWT access/refresh token、logout revoke。
|
- `auth`:native email/password 登入、JWT access/refresh token、logout revoke。
|
||||||
- `member`:目前登入會員的 profile 讀寫。
|
- `member`:目前登入會員的 profile 讀寫。
|
||||||
- `permission`:permission catalog 與目前會員權限查詢。
|
- `permission`:permission catalog 與目前會員權限查詢。
|
||||||
- `threads automation`:Threads 帳號、OAuth/API 診斷、發文 queue、補庫存、智慧時段、頻率護欄、成效追蹤與語調庫。
|
|
||||||
|
|
||||||
暫時不包含 template-monorepo 裡較重的 OAuth / OTP / MFA / Zitadel 整合,也不包含 notification、Playwright worker。這些之後要接時再按服務邊界新增。
|
暫時不包含 template-monorepo 裡較重的 OAuth / OTP / MFA / Zitadel 整合,也不包含 notification、Playwright worker。這些之後要接時再按服務邊界新增。
|
||||||
|
|
||||||
|
|
@ -24,13 +23,6 @@ go mod download
|
||||||
make run
|
make run
|
||||||
```
|
```
|
||||||
|
|
||||||
正式前端:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make web-dev # Vite dev server :5173,proxy 到 :8890
|
|
||||||
make web-build # TypeScript + Vite build
|
|
||||||
```
|
|
||||||
|
|
||||||
預設服務:
|
預設服務:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|
@ -69,10 +61,10 @@ HAIXUN_8D_MIN_SAMPLES=1 # 驗證期預設 1;要嚴格一點可調高
|
||||||
前端在人設詳情頁按「開始 8D 分析」後,任務會進入:
|
前端在人設詳情頁按「開始 8D 分析」後,任務會進入:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
準備爬蟲 -> 抓取公開樣本 -> AI 8D -> 儲存策略
|
確認連線 -> 抓取樣本 -> AI 8D -> 儲存策略
|
||||||
```
|
```
|
||||||
|
|
||||||
人設 8D 分析**不需** Chrome extension session;Node worker 以匿名 Playwright 讀取對標帳號的公開個人頁。若公開頁無法讀到足夠樣本,job 會標記為 `failed` 並顯示原因,不會停在等待狀態。AI 分析使用會員設定頁的研究用 provider / API key(有選定經營帳號時沿用該帳號設定)。
|
目前 Node worker 先用 Playwright 抓 Threads 公開頁樣本並產生可驗證的 8D 結構;若公開頁無法讀到足夠樣本,job 會標記為 `failed` 並顯示原因,不會停在等待狀態。
|
||||||
|
|
||||||
## 專案結構
|
## 專案結構
|
||||||
|
|
||||||
|
|
@ -80,8 +72,6 @@ HAIXUN_8D_MIN_SAMPLES=1 # 驗證期預設 1;要嚴格一點可調高
|
||||||
haixun-backend/
|
haixun-backend/
|
||||||
gateway.go # go-zero server 入口
|
gateway.go # go-zero server 入口
|
||||||
Makefile # gen-api / fmt / test / run
|
Makefile # gen-api / fmt / test / run
|
||||||
web/ # 正式巡樓 Console(Vite + React + TypeScript)
|
|
||||||
dev-console/ # 本機診斷用最小開發面板,不是產品 UI
|
|
||||||
etc/ # runtime config
|
etc/ # runtime config
|
||||||
generate/
|
generate/
|
||||||
api/ # goctl .api 定義
|
api/ # goctl .api 定義
|
||||||
|
|
@ -97,11 +87,6 @@ haixun-backend/
|
||||||
auth/ # JWT token issue/refresh/logout + Redis revoke store
|
auth/ # JWT token issue/refresh/logout + Redis revoke store
|
||||||
member/ # Native member profile + password hash
|
member/ # Native member profile + password hash
|
||||||
permission/ # Permission catalog + role permission mapping
|
permission/ # Permission catalog + role permission mapping
|
||||||
publish_queue/ # Threads 發文 queue、guarded transition、retry/missed
|
|
||||||
publish_inventory/ # 自動補庫存 policy
|
|
||||||
publish_guard/ # 發文頻率護欄與 pause/resume
|
|
||||||
publish_queue_event/ # Queue 狀態轉移與告警事件
|
|
||||||
style_preset/ # 人設語調 preset、CTA、禁用詞
|
|
||||||
worker/ # 常駐背景 worker / scheduler / reaper
|
worker/ # 常駐背景 worker / scheduler / reaper
|
||||||
library/ # 最小 runtime library
|
library/ # 最小 runtime library
|
||||||
response/ # 統一 JSON response envelope
|
response/ # 統一 JSON response envelope
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
bootstrap.LoadInfraEnv()
|
|
||||||
|
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
printUsage()
|
printUsage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
@ -39,7 +37,7 @@ func runInit(args []string) error {
|
||||||
configFile := fs.String("f", "etc/gateway.yaml", "config file")
|
configFile := fs.String("f", "etc/gateway.yaml", "config file")
|
||||||
tenantID := fs.String("tenant", envOr("INIT_TENANT_ID", "default"), "tenant id for admin and role permissions")
|
tenantID := fs.String("tenant", envOr("INIT_TENANT_ID", "default"), "tenant id for admin and role permissions")
|
||||||
email := fs.String("email", envOr("INIT_ADMIN_EMAIL", "admin@30cm.net"), "bootstrap admin email")
|
email := fs.String("email", envOr("INIT_ADMIN_EMAIL", "admin@30cm.net"), "bootstrap admin email")
|
||||||
password := fs.String("password", envOr("INIT_ADMIN_PASSWORD", ""), "bootstrap admin password")
|
password := fs.String("password", envOr("INIT_ADMIN_PASSWORD", "Fafafa54088"), "bootstrap admin password")
|
||||||
displayName := fs.String("display-name", envOr("INIT_ADMIN_DISPLAY_NAME", "Admin"), "bootstrap admin display name")
|
displayName := fs.String("display-name", envOr("INIT_ADMIN_DISPLAY_NAME", "Admin"), "bootstrap admin display name")
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := fs.Parse(args); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"haixun-backend/internal/bootstrap"
|
|
||||||
"haixun-backend/internal/config"
|
"haixun-backend/internal/config"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
|
|
||||||
|
|
@ -19,12 +18,11 @@ var configFile = flag.String("f", "etc/gateway.worker.yaml", "config file")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
bootstrap.LoadInfraEnv()
|
|
||||||
|
|
||||||
var c config.Config
|
var c config.Config
|
||||||
conf.MustLoad(*configFile, &c, conf.UseEnv())
|
conf.MustLoad(*configFile, &c, conf.UseEnv())
|
||||||
if !c.JobWorker.Enabled && !c.JobScheduler.Enabled && !c.JobReaper.Enabled && !c.ThreadsTokenRefresh.Enabled && !c.ThreadsPublishQueue.Enabled {
|
if !c.JobWorker.Enabled {
|
||||||
fmt.Fprintln(os.Stderr, "[worker] at least one worker, scheduler, reaper, token sweeper, or publish queue sweeper must be enabled")
|
fmt.Fprintln(os.Stderr, "[worker] JobWorker.Enabled must be true")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,13 +30,10 @@ func main() {
|
||||||
defer sc.Close(context.Background())
|
defer sc.Close(context.Background())
|
||||||
|
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
"[worker] started type=%s (job=%v scheduler=%v reaper=%v token_sweeper=%v publish_sweeper=%v)\n",
|
"[worker] started type=%s (scheduler=%v reaper=%v)\n",
|
||||||
c.JobWorker.WorkerType,
|
c.JobWorker.WorkerType,
|
||||||
c.JobWorker.Enabled,
|
|
||||||
c.JobScheduler.Enabled,
|
c.JobScheduler.Enabled,
|
||||||
c.JobReaper.Enabled,
|
c.JobReaper.Enabled,
|
||||||
c.ThreadsTokenRefresh.Enabled,
|
|
||||||
c.ThreadsPublishQueue.Enabled,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ch := make(chan os.Signal, 1)
|
ch := make(chan os.Signal, 1)
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
# 巡樓 Dev Console
|
|
||||||
|
|
||||||
重做正式前端前的**最小開發面板**:登入、人設 CRUD、8D 公開爬蟲任務與 job 輪詢。
|
|
||||||
|
|
||||||
## 啟動
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 後端 API(另開終端)
|
|
||||||
cd backend
|
|
||||||
go run gateway.go -f etc/gateway.yaml
|
|
||||||
|
|
||||||
# 2. Node 8D worker(另開終端,人設 8D 需要)
|
|
||||||
cd backend/worker
|
|
||||||
npm install
|
|
||||||
npm run style-8d
|
|
||||||
|
|
||||||
# 3. Dev Console
|
|
||||||
cd backend/dev-console
|
|
||||||
npm install
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
瀏覽器開啟 http://127.0.0.1:5173 。API 透過 Vite proxy 轉到 `http://127.0.0.1:8890`,無需處理 CORS。
|
|
||||||
|
|
||||||
## 使用順序
|
|
||||||
|
|
||||||
1. 註冊或登入(tenant 預設 `default`)
|
|
||||||
2. **AI 設定**:選研究 provider → 貼 API key → **讀取模型列表** → 選 model → **儲存**
|
|
||||||
3. 建立人設 → 點選列表中的一筆
|
|
||||||
4. 填對標 Threads 帳號 → **開始 8D**
|
|
||||||
5. 自動輪詢 job;完成後下方人設詳情會出現 `style_profile`
|
|
||||||
|
|
||||||
## 注意
|
|
||||||
|
|
||||||
- 8D 分析使用**研究用** provider / model + API key(與 Chrome session 無關)
|
|
||||||
- 若尚無經營帳號,Dev Console 會自動建立「Dev Console」帳號(AI key 實際存會員 scope)
|
|
||||||
- 僅供本機開發,不是產品 UI
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,225 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="zh-Hant">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>巡樓 Dev Console</title>
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg: #f4f6f8;
|
|
||||||
--card: #fff;
|
|
||||||
--ink: #1a2332;
|
|
||||||
--muted: #5a6578;
|
|
||||||
--line: #d8dee8;
|
|
||||||
--brand: #2a9d8f;
|
|
||||||
--danger: #c0392b;
|
|
||||||
--ok: #1e7a4c;
|
|
||||||
--mono: ui-monospace, 'SF Mono', Menlo, monospace;
|
|
||||||
--sans: system-ui, -apple-system, sans-serif;
|
|
||||||
}
|
|
||||||
* { box-sizing: border-box; }
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: var(--sans);
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--ink);
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
header {
|
|
||||||
padding: 1rem 1.25rem;
|
|
||||||
background: var(--card);
|
|
||||||
border-bottom: 1px solid var(--line);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
header h1 { margin: 0; font-size: 1.1rem; }
|
|
||||||
header p { margin: 0.15rem 0 0; color: var(--muted); font-size: 0.85rem; }
|
|
||||||
main {
|
|
||||||
max-width: 960px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 1rem 1.25rem 3rem;
|
|
||||||
display: grid;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
background: var(--card);
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 1rem 1.1rem;
|
|
||||||
}
|
|
||||||
section h2 {
|
|
||||||
margin: 0 0 0.75rem;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
label { font-size: 0.82rem; color: var(--muted); min-width: 5.5rem; }
|
|
||||||
input, select, textarea {
|
|
||||||
font: inherit;
|
|
||||||
padding: 0.45rem 0.6rem;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #fff;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
input { flex: 1; }
|
|
||||||
button {
|
|
||||||
font: inherit;
|
|
||||||
padding: 0.45rem 0.85rem;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
border-radius: 999px;
|
|
||||||
background: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button:hover { border-color: var(--brand); color: var(--brand); }
|
|
||||||
button.primary {
|
|
||||||
background: var(--brand);
|
|
||||||
border-color: var(--brand);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
button.primary:hover { filter: brightness(1.05); color: #fff; }
|
|
||||||
button.danger { color: var(--danger); }
|
|
||||||
.status { font-size: 0.82rem; color: var(--muted); }
|
|
||||||
.status.ok { color: var(--ok); }
|
|
||||||
.status.err { color: var(--danger); }
|
|
||||||
.list {
|
|
||||||
margin: 0.5rem 0 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.list li {
|
|
||||||
padding: 0.55rem 0.75rem;
|
|
||||||
border-bottom: 1px solid var(--line);
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 0.75rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
.list li:last-child { border-bottom: none; }
|
|
||||||
.list li:hover { background: #f0faf8; }
|
|
||||||
.list li.active { background: #e6f5f2; font-weight: 600; }
|
|
||||||
.list li .meta { color: var(--muted); font-size: 0.78rem; }
|
|
||||||
pre, .log {
|
|
||||||
margin: 0.5rem 0 0;
|
|
||||||
padding: 0.75rem;
|
|
||||||
background: #0f1419;
|
|
||||||
color: #d6e2f0;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-family: var(--mono);
|
|
||||||
font-size: 0.78rem;
|
|
||||||
overflow: auto;
|
|
||||||
max-height: 280px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
.steps { margin: 0.5rem 0 0; font-size: 0.85rem; }
|
|
||||||
.steps div { padding: 0.2rem 0; }
|
|
||||||
.badge {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.1rem 0.45rem;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 0.72rem;
|
|
||||||
background: #eef2f7;
|
|
||||||
}
|
|
||||||
.badge.running { background: #fff3cd; }
|
|
||||||
.badge.succeeded, .badge.completed { background: #d4edda; }
|
|
||||||
.badge.failed { background: #f8d7da; }
|
|
||||||
.badge.queued, .badge.pending { background: #e8eef5; }
|
|
||||||
textarea { width: 100%; min-height: 120px; font-family: var(--mono); font-size: 0.78rem; }
|
|
||||||
.hint { font-size: 0.8rem; color: var(--muted); margin-top: 0.35rem; }
|
|
||||||
</style>
|
|
||||||
<script type="module" crossorigin src="/assets/index-56hNfpGK.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<div>
|
|
||||||
<h1>巡樓 Dev Console</h1>
|
|
||||||
<p>暫用開發面板 · 人設 + 8D 公開爬蟲 · 重做前端前夠用就好</p>
|
|
||||||
</div>
|
|
||||||
<div class="status" id="connStatus">尚未連線</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<section>
|
|
||||||
<h2>1. 登入</h2>
|
|
||||||
<div class="row">
|
|
||||||
<label for="tenantId">Tenant</label>
|
|
||||||
<input id="tenantId" value="default" />
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<label for="email">Email</label>
|
|
||||||
<input id="email" type="email" placeholder="you@example.com" />
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<label for="password">密碼</label>
|
|
||||||
<input id="password" type="password" placeholder="至少 8 碼" />
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<button class="primary" id="btnLogin">登入</button>
|
|
||||||
<button id="btnRegister">註冊</button>
|
|
||||||
<button id="btnLogout" class="danger">登出</button>
|
|
||||||
<span class="status" id="authStatus"></span>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>2. 人設</h2>
|
|
||||||
<div class="row">
|
|
||||||
<label for="displayName">名稱</label>
|
|
||||||
<input id="displayName" placeholder="新人設" />
|
|
||||||
<button class="primary" id="btnCreatePersona">建立</button>
|
|
||||||
<button id="btnRefreshPersonas">重新載入</button>
|
|
||||||
</div>
|
|
||||||
<ul class="list" id="personaList"></ul>
|
|
||||||
<p class="hint">點選一筆人設後,下方可跑 8D 分析。</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>3. 8D 風格分析(公開爬蟲,不需 session)</h2>
|
|
||||||
<div class="row">
|
|
||||||
<label for="benchmark">對標帳號</label>
|
|
||||||
<input id="benchmark" placeholder="@username" />
|
|
||||||
<button class="primary" id="btnStart8d" disabled>開始 8D</button>
|
|
||||||
</div>
|
|
||||||
<p class="hint">需先啟動 Go API(:8890)與 Node worker(style-8d)。設定頁要有研究用 AI API key。</p>
|
|
||||||
<div class="status" id="jobHint"></div>
|
|
||||||
<div class="row" style="margin-top: 0.5rem">
|
|
||||||
<label for="jobId">Job ID</label>
|
|
||||||
<input id="jobId" placeholder="自動填入或手動貼上" />
|
|
||||||
<button id="btnPollJob">查詢一次</button>
|
|
||||||
<button id="btnAutoPoll">自動輪詢</button>
|
|
||||||
</div>
|
|
||||||
<div class="steps" id="jobSteps"></div>
|
|
||||||
<pre id="jobDetail">(尚無任務)</pre>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>4. 人設詳情</h2>
|
|
||||||
<div class="row">
|
|
||||||
<button id="btnReloadPersona" disabled>重新載入選中人設</button>
|
|
||||||
<span class="status" id="personaMeta"></span>
|
|
||||||
</div>
|
|
||||||
<textarea id="personaDetail" readonly placeholder="選中人設後顯示 style_profile 等欄位"></textarea>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>Log</h2>
|
|
||||||
<pre class="log" id="log"></pre>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"name": "haixun-dev-console",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "vite build",
|
|
||||||
"preview": "vite preview"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"vite": "^6.2.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,176 @@
|
||||||
|
# 全 Phase 新增功能說明
|
||||||
|
|
||||||
|
## Phase 1 — TA 精準度
|
||||||
|
|
||||||
|
### 1. Semantic Embedding Reranker
|
||||||
|
|
||||||
|
**ScanPost entity 新增欄位**(`internal/model/scan_post/domain/entity/post.go`):
|
||||||
|
|
||||||
|
| 欄位 | 型別 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `Embedding` | `[]float32` | OpenAI text-embedding-3-small 向量 |
|
||||||
|
| `SemanticScore` | `int` | 語意相似度分數(0–100) |
|
||||||
|
| `EngagementPredicted` | `int` | 預測互動率 |
|
||||||
|
| `AudienceQualityScore` | `int` | 受眾品質分數(0–100) |
|
||||||
|
| `AuthorID` | `string` | 作者 ID(Threads user ID) |
|
||||||
|
| `AuthorAvatar` | `string` | 作者頭像 URL |
|
||||||
|
| `AuthorFollowers` | `int` | 作者粉絲數 |
|
||||||
|
|
||||||
|
`ScanCandidate` struct 同步新增對應欄位(`internal/library/placement/dual_track.go`)。
|
||||||
|
|
||||||
|
### 2. Embedding Client
|
||||||
|
|
||||||
|
```go
|
||||||
|
// internal/library/embedding/client.go
|
||||||
|
func NewClient(apiKey, model string) *Client
|
||||||
|
func (c *Client) Generate(ctx context.Context, text string) ([]float32, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 預設使用 OpenAI `text-embedding-3-small`(可降級 local BGE-M3)
|
||||||
|
- Scorer: `CosineSimilarity(a, b []float32) float64`
|
||||||
|
|
||||||
|
### 3. Placement Score V2
|
||||||
|
|
||||||
|
```go
|
||||||
|
// internal/library/placement/dual_track.go
|
||||||
|
func computePlacementScore(post *ScanCandidate, member productContext, brand productMeta,
|
||||||
|
semanticScore, predictedEngagement, audienceQuality *int) int
|
||||||
|
```
|
||||||
|
|
||||||
|
整合三項 pointer 參數(nil 表示欄位不存在,跳過該項):
|
||||||
|
|
||||||
|
- **語意相似度**(`SemanticScore`):Embedding cosine → 0–100
|
||||||
|
- **預測互動率**(`EngagementPredicted`):like+reply → 對數正規化 0–100
|
||||||
|
- **受眾品質**(`AudienceQualityScore`):`computeAudienceQuality()` heuristic
|
||||||
|
|
||||||
|
權重:語意 20% / 互動 30% / 品質 10% / 舊有 productFit + solvedBy + recency 40%。
|
||||||
|
|
||||||
|
```go
|
||||||
|
func computeAudienceQuality(followers int) int
|
||||||
|
```
|
||||||
|
|
||||||
|
Heuristic:followers 在 500–50k 之間品質最佳,過低或過高遞減。
|
||||||
|
|
||||||
|
### 4. Creator Lookalike
|
||||||
|
|
||||||
|
**端點**: `POST /api/v1/scan-post/lookalike`(需 JWT)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "source_post_id": "uuid", "brand_id": "...", "limit": 10 }
|
||||||
|
|
||||||
|
→ { "list": [{ "post_id", "author_id", "author_name", "similarity", ... }] }
|
||||||
|
```
|
||||||
|
|
||||||
|
**演算法**: cosine similarity(門檻 ≥0.3),同 brand 內找 embedding 最接近的貼文回傳。
|
||||||
|
|
||||||
|
| 檔案 | |
|
||||||
|
|------|--|
|
||||||
|
| Handler | `internal/handler/scan_post/lookalike_handler.go` |
|
||||||
|
| Logic | `internal/logic/scan_post/lookalike_logic.go` |
|
||||||
|
| `cosineSimilarity` | `internal/logic/scan_post/lookalike_logic.go` — 全 Go 實作 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 — 營運流程
|
||||||
|
|
||||||
|
### 2.1 Creator CRM
|
||||||
|
|
||||||
|
**完整四層 model** `crm_contact`:
|
||||||
|
|
||||||
|
```
|
||||||
|
internal/model/crm_contact/
|
||||||
|
domain/entity/entity.go
|
||||||
|
domain/repository/repository.go
|
||||||
|
domain/usecase/usecase.go
|
||||||
|
usecase/usecase.go
|
||||||
|
repository/mongo.go
|
||||||
|
```
|
||||||
|
|
||||||
|
**端點**: `/api/v1/crm/contacts`(需 JWT)
|
||||||
|
|
||||||
|
| Method | Path | 用途 |
|
||||||
|
|--------|------|------|
|
||||||
|
| GET | `/` | List(`page`/`pageSize`/`brand_id`/`status`/`tag`) |
|
||||||
|
| POST | `/` | 新增聯絡人(`author_id` + `author_name`) |
|
||||||
|
| GET | `/:id` | 取得單筆 |
|
||||||
|
| PATCH | `/:id` | 更新(`notes`/`tags`/`status`) |
|
||||||
|
| DELETE | `/:id` | 刪除 |
|
||||||
|
|
||||||
|
**CRMContact 狀態機**: `lead` → `contacted` → `responded` → `converted` / `archived`
|
||||||
|
|
||||||
|
### 2.2 Publish Analytics
|
||||||
|
|
||||||
|
**完整四層 model** `publish_analytics`:
|
||||||
|
|
||||||
|
**Checkpoint 常數**(`internal/model/publish_analytics/usecase/usecase.go`): `+1h`、`+24h`、`+7d`
|
||||||
|
|
||||||
|
**方法**:
|
||||||
|
```go
|
||||||
|
RecordCheckpoint(ctx, tenantID, ownerUID, summary) (*PublishAnalyticsSummary, error)
|
||||||
|
ListByMedia(ctx, tenantID, ownerUID, mediaID) ([]PublishAnalyticsSummary, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Worker handler** `publish-analytics`(step `check`):
|
||||||
|
|
||||||
|
呼叫 Threads API `GET /{media-id}?fields=like_count,reply_count,repost_count,quote_count` → 存入 `publish_analytics`。
|
||||||
|
|
||||||
|
### 2.3 Threads API Token 刷新
|
||||||
|
|
||||||
|
**底層 API**(`internal/library/threadsapi/media.go`):
|
||||||
|
```go
|
||||||
|
RefreshAccessToken(ctx, currentToken) (*TokenRefreshResult, error)
|
||||||
|
// GET /v1.0/refresh_access_token?grant_type=th_refresh_token
|
||||||
|
```
|
||||||
|
|
||||||
|
**儲存 Token**:
|
||||||
|
```go
|
||||||
|
// Repository
|
||||||
|
SaveAPIAccessToken(ctx, accountID, accessToken, expiresAt) (*entity.Secrets, error)
|
||||||
|
// Usecase
|
||||||
|
SaveAPIAccessToken(ctx, accountID, accessToken, expiresAt) (*SecretsData, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Worker handler** `refresh-threads-token`(step `refresh`):讀取 secrets → refresh → 儲存。
|
||||||
|
|
||||||
|
### 2.4 Rate Limiter
|
||||||
|
|
||||||
|
```go
|
||||||
|
// internal/library/ratelimit/token_bucket.go
|
||||||
|
func NewTokenBucket(rate float64, burst int) *TokenBucket
|
||||||
|
func (tb *TokenBucket) Allow() bool
|
||||||
|
func (tb *TokenBucket) AllowN(n int) bool
|
||||||
|
```
|
||||||
|
|
||||||
|
- In-memory token bucket(非 Redis),thread-safe
|
||||||
|
- `rate` = tokens/sec,`burst` = 最大 burst
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Infrastructure
|
||||||
|
|
||||||
|
### Job Templates
|
||||||
|
|
||||||
|
`internal/model/job/usecase/usecase.go` 新增兩個 template constants + 對應 `Ensure*` methods + `template()` functions:
|
||||||
|
|
||||||
|
| Template Type | Steps | Timeout | Retry |
|
||||||
|
|---------------|-------|---------|-------|
|
||||||
|
| `refresh-threads-token` | `refresh`(go) | 120s | 3 次(30/120/300s) |
|
||||||
|
| `publish-analytics` | `check`(go) | 120s | 2 次(60s) |
|
||||||
|
|
||||||
|
### Worker Handlers 註冊
|
||||||
|
|
||||||
|
`internal/svc/service_context.go` 中註冊:
|
||||||
|
|
||||||
|
```go
|
||||||
|
jobworker.RegisterRefreshThreadsTokenHandler(runner, RefreshThreadsTokenDeps{...})
|
||||||
|
jobworker.RegisterPublishAnalyticsHandler(runner, PublishAnalyticsDeps{...})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Worker Helper
|
||||||
|
|
||||||
|
```go
|
||||||
|
// internal/worker/job/expand_graph.go
|
||||||
|
func int64Field(payload map[string]any, key string) int64
|
||||||
|
```
|
||||||
|
|
||||||
|
從 job payload 讀 `int64`,支援 `float64` / `int64` / `int` / `json.Number`。
|
||||||
|
|
@ -0,0 +1,512 @@
|
||||||
|
# 海巡獲客計畫:知識圖譜 + 雙軌爬取 + 島民交接
|
||||||
|
|
||||||
|
> 在既有「背景 Job + 島民交接」上,新增 Topic Knowledge Graph(Brave 驅動)與**雙維度 Tag**(相關 + 近期)+ **雙軌海巡**,強化流程 B 的痛點發現、關鍵字精準度與**產品-痛點匹配**驗證。
|
||||||
|
|
||||||
|
## 北極星
|
||||||
|
|
||||||
|
海巡找到的貼文/留言,**你的產品是否真的解得了那個問題**(可置入、可回覆、可追蹤)。
|
||||||
|
|
||||||
|
流程 B 主線:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Brave 知識圖譜擴散(周邊延伸)
|
||||||
|
→ 衍生雙維度搜尋 tag(相關詞 + 近期求助詞)
|
||||||
|
→ 使用者手動勾選
|
||||||
|
→ 每個 tag 雙軌爬 Threads(相關軌 + 近期軌,7d 優先 / 30d 補充)
|
||||||
|
→ productFitScore 篩選
|
||||||
|
→ 島民協助撰寫獲客留言
|
||||||
|
```
|
||||||
|
|
||||||
|
## 已拍板決策
|
||||||
|
|
||||||
|
| 項目 | 決策 |
|
||||||
|
|------|------|
|
||||||
|
| 圖譜深度 | **3 層、範圍廣**:核心 → 成因/症狀 → 相鄰情境 |
|
||||||
|
| 進海巡的 tag | **使用者手動勾選**(圖譜 UI 多選;島民可 toggle,不預設全選) |
|
||||||
|
| 近期窗口 | **7 天內為重點**;不足時補充至 **30 天**;超過 30 天排除 |
|
||||||
|
| Brave 預算 | 中等,每輪知識擴展 **10–15 次查詢**(不足可 supplemental 1 輪) |
|
||||||
|
| 痛點 tag 候選 | 圖譜衍生 **≥12 候選**,其中痛點/求助類 **≥8** |
|
||||||
|
| 流程 A | 保留 `style-8d` 捷徑;matrix + 留言收集疊加於 Phase 2 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 根因診斷(舊系統痛點少、關鍵字不準)
|
||||||
|
|
||||||
|
對照舊 Next.js([`lib/ai/prompts/research-map-placement.ts`](../../lib/ai/prompts/research-map-placement.ts)、[`lib/services/scan-tasks.ts`](../../lib/services/scan-tasks.ts)、[`lib/ai/analyze-topic.ts`](../../lib/ai/analyze-topic.ts)):
|
||||||
|
|
||||||
|
| 現象 | 根因 | 新系統對策 |
|
||||||
|
|------|------|------------|
|
||||||
|
| 痛點只抓到 1–2 個 | Placement 壓 `suggestedTags` 至 **2~4**;`PLACEMENT_QUERY_MAX = 8` | 圖譜衍生 ≥8 痛點 tag + supplemental 補充迴圈 |
|
||||||
|
| 關鍵字不夠精準 | AI 憑種子詞推測,無外部知識 | Brave `knowledge_expand` 建 TKG,節點附 `evidence[]` |
|
||||||
|
| 只有「最相關」 | Recency 只是加分,無獨立近期軌 | **Tag 層**分 `relevance` / `recency`;**Crawl 層**雙軌必跑 |
|
||||||
|
| 沒有周邊延伸 | Brave 只做 `site:threads.net` | `knowledge_expand` 做領域知識(成因、懷孕、換季…)再衍生 tag |
|
||||||
|
|
||||||
|
新後端原則見 [`AGENTS.md`](../AGENTS.md):**複製模式,不複製舊業務**——移植 Playwright/過濾規則,用 Mongo + Job 重建。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 架構總覽
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph input [輸入]
|
||||||
|
Seed["種子詞"]
|
||||||
|
ProductBrief["product_brief"]
|
||||||
|
Persona["人設"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph tkg [知識圖譜]
|
||||||
|
ExpandJob["expand-graph job"]
|
||||||
|
BraveK["Brave knowledge_expand"]
|
||||||
|
TKG["topic_knowledge_graphs"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph derive [Tag衍生]
|
||||||
|
DeriveFn["deriveSearchTagsFromGraph"]
|
||||||
|
RelQ["relevanceQueries"]
|
||||||
|
RecQ["recencyQueries"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph select [使用者選擇]
|
||||||
|
GraphUI["圖譜 UI 勾選節點/tag"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph scan [海巡]
|
||||||
|
ScanJob["scan job 每tag雙軌"]
|
||||||
|
Posts["scan_posts"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph outcome [驗收]
|
||||||
|
Fit["productFitScore"]
|
||||||
|
Outreach["outreach + 島民留言"]
|
||||||
|
end
|
||||||
|
|
||||||
|
Seed --> ExpandJob
|
||||||
|
ProductBrief --> ExpandJob
|
||||||
|
Persona --> ExpandJob
|
||||||
|
ExpandJob --> BraveK --> TKG
|
||||||
|
TKG --> DeriveFn
|
||||||
|
DeriveFn --> RelQ
|
||||||
|
DeriveFn --> RecQ
|
||||||
|
RelQ --> GraphUI
|
||||||
|
RecQ --> GraphUI
|
||||||
|
GraphUI --> ScanJob --> Posts --> Fit --> Outreach
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tag 產生完整流水線
|
||||||
|
|
||||||
|
Tag **不是** AI 一次吐 2~4 個,而是五段流水線產出:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
S["1 種子詞+brief"] --> A["2 AI核心地圖"]
|
||||||
|
A --> B["3 Brave knowledge_expand"]
|
||||||
|
B --> G["4 合成TKG三層"]
|
||||||
|
G --> D["5 deriveSearchTagsFromGraph"]
|
||||||
|
D --> R["relevanceQueries"]
|
||||||
|
D --> C["recencyQueries"]
|
||||||
|
```
|
||||||
|
|
||||||
|
| 步驟 | 做什麼 | 產出 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 1 | 讀 `seed_query`、`product_brief`、`target_audience` | 輸入包 |
|
||||||
|
| 2 | AI 產核心 questions/pillars/exclusions | 研究地圖骨架 |
|
||||||
|
| 3 | Brave 10–15 次**一般網搜**(非 threadsOnly) | snippets → 候選節點 |
|
||||||
|
| 4 | AI 合成 TKG(L0/L1/L2)+ `productFitScore` + `evidence[]` | `topic_knowledge_graphs` |
|
||||||
|
| 5 | 每節點壓成 2~8 字真人搜尋詞,分兩套 | `derivedTags` |
|
||||||
|
|
||||||
|
### 雙維度 Tag(相關 + 近期都要)
|
||||||
|
|
||||||
|
每個圖譜節點衍生:
|
||||||
|
|
||||||
|
| 維度 | 用途 | 寫法範例 |
|
||||||
|
|------|------|----------|
|
||||||
|
| **`relevanceQueries`** | 相關軌:短詞、高命中 | `敏感肌`、`屏障受損` |
|
||||||
|
| **`recencyQueries`** | 近期軌:求助語境 + 時間窗 | `敏感肌 請問`、`換季泛紅 推薦` |
|
||||||
|
|
||||||
|
- `recencyQueries` 在 Brave `threads_discover` 時加 `after:{7天前日期}`(參考舊 [`scan-web-discover.ts`](../../lib/services/scan-web-discover.ts) `buildPlacementKeywordQueries`)
|
||||||
|
- 候選總量:**≥12 tag**(痛點/求助類 **≥8**);**使用者勾選後才 crawl**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 痛點 Tag 保底機制
|
||||||
|
|
||||||
|
解決「只抓到一兩個痛點」:
|
||||||
|
|
||||||
|
```text
|
||||||
|
expand-graph 完成 → deriveSearchTagsFromGraph
|
||||||
|
IF 痛點/求助類 tag 數 < 8:
|
||||||
|
→ supplemental_round(最多 1 次,Brave +5 查詢)
|
||||||
|
→ 追加查詢例:{seed} 困擾、{seed} 求助、{L2節點} 請問、{seed} 推薦
|
||||||
|
→ AI 補節點 + 補 derivedTags
|
||||||
|
IF 仍 < 8:
|
||||||
|
→ job 標 warning;UI + 島民提示「可重跑 expand 或手動加種子詞」
|
||||||
|
```
|
||||||
|
|
||||||
|
| 指標 | 舊系統 | 新系統 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| Placement suggestedTags | 2~4 | 不沿用此上限 |
|
||||||
|
| 搜尋任務上限 | 8 | 候選 ≥12,實 crawl = 勾選數 |
|
||||||
|
| 痛點類最低 | 無保證 | **≥8**(含 supplemental) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Topic Knowledge Graph(TKG)
|
||||||
|
|
||||||
|
### Mongo collection:`topic_knowledge_graphs`
|
||||||
|
|
||||||
|
綁 `persona_id` + `seed_query`。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"seed": "敏感肌",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "n1",
|
||||||
|
"label": "敏感肌",
|
||||||
|
"nodeKind": "pain",
|
||||||
|
"type": "core",
|
||||||
|
"layer": 0,
|
||||||
|
"placementValue": "high",
|
||||||
|
"productFitScore": 95,
|
||||||
|
"selectedForScan": false,
|
||||||
|
"evidence": [],
|
||||||
|
"derivedTags": {
|
||||||
|
"relevance": ["敏感肌"],
|
||||||
|
"recency": ["敏感肌 請問", "敏感肌 推薦"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "n2",
|
||||||
|
"label": "懷孕嗅覺敏感",
|
||||||
|
"nodeKind": "cause",
|
||||||
|
"type": "cause",
|
||||||
|
"layer": 2,
|
||||||
|
"relation": "可能成因",
|
||||||
|
"placementValue": "medium",
|
||||||
|
"productFitScore": 40,
|
||||||
|
"selectedForScan": false,
|
||||||
|
"evidence": [{ "url": "...", "snippet": "..." }],
|
||||||
|
"derivedTags": {
|
||||||
|
"relevance": ["懷孕皮膚癢", "嗅覺敏感"],
|
||||||
|
"recency": ["懷孕 皮膚 癢 請益"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "n3",
|
||||||
|
"label": "屏障修復原理",
|
||||||
|
"nodeKind": "knowledge",
|
||||||
|
"type": "mechanism",
|
||||||
|
"layer": 1,
|
||||||
|
"productFitScore": 70,
|
||||||
|
"selectedForScan": false,
|
||||||
|
"derivedTags": {
|
||||||
|
"relevance": ["屏障受損"],
|
||||||
|
"recency": ["屏障受損 怎麼辦"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{ "from": "n1", "to": "n2", "relation": "可能因" },
|
||||||
|
{ "from": "n1", "to": "n3", "relation": "機制" }
|
||||||
|
],
|
||||||
|
"braveSources": [{ "query": "敏感肌 懷孕 原因", "snippet": "...", "url": "..." }],
|
||||||
|
"painTagCount": 9,
|
||||||
|
"generatedAt": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 三層擴散
|
||||||
|
|
||||||
|
```text
|
||||||
|
L0 核心:敏感肌
|
||||||
|
L1 直接相關:屏障受損、換季泛紅、刺癢
|
||||||
|
L2 周邊情境:懷孕荷爾蒙、嗅覺敏感、壓力熬夜、換洗臉產品過敏 …
|
||||||
|
```
|
||||||
|
|
||||||
|
### 節點語意
|
||||||
|
|
||||||
|
| 欄位 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| `nodeKind` | `pain`(痛點/求助)、`knowledge`(科普延伸)、`cause`、`symptom` |
|
||||||
|
| `placementValue` | 建議優先級,**不決定是否海巡** |
|
||||||
|
| `selectedForScan` | 使用者勾選後 `true`,才進 `scan` payload |
|
||||||
|
| `productFitScore` | 依 `product_brief`:產品解不解得了 |
|
||||||
|
| `derivedTags` | `relevance` + `recency` 兩套查詢詞 |
|
||||||
|
| `evidence[]` | L1/L2 必填(Brave snippet 可追溯) |
|
||||||
|
|
||||||
|
- `knowledge` 節點:延伸話題/科普靈感,**預設不勾選**;若 snippet 含求助語境可升級為 `pain`
|
||||||
|
- `knowledge` 不強制進 placement crawl,除非使用者勾選且 `productFitScore` 達標
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Brave 雙模式
|
||||||
|
|
||||||
|
| 模式 | `threadsOnly` | 用途 |
|
||||||
|
|------|---------------|------|
|
||||||
|
| `knowledge_expand` | `false` | 建 TKG,找成因/周邊/知識 |
|
||||||
|
| `threads_discover` | `true` | 海巡時找 Threads 貼文 |
|
||||||
|
|
||||||
|
### L0/L1 查詢模板(plan_queries,上限 15/輪)
|
||||||
|
|
||||||
|
```text
|
||||||
|
{seed} 常見原因
|
||||||
|
{seed} 什麼情況會
|
||||||
|
{seed} 初期 症狀
|
||||||
|
{seed} 怎麼改善 困擾
|
||||||
|
{seed} 求助 推薦
|
||||||
|
```
|
||||||
|
|
||||||
|
### L2 周邊擴散查詢池(從 brief/受眾推導)
|
||||||
|
|
||||||
|
```text
|
||||||
|
{seed} 懷孕 相關
|
||||||
|
{seed} 壓力 熬夜
|
||||||
|
{seed} 換產品 過敏
|
||||||
|
{seed} 與 {受眾場景} 的關係
|
||||||
|
{L1節點} 原因
|
||||||
|
{L1節點} 困擾
|
||||||
|
```
|
||||||
|
|
||||||
|
Brave 回傳 title/snippet/url → AI 萃取節點與邊 → 寫入 TKG。實作:`internal/library/knowledge/` + Brave adapter(`BRAVE_SEARCH_API_KEY`;參考舊 [`lib/services/web-search.ts`](../../lib/services/web-search.ts))。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 完整範例:敏感肌 Walkthrough
|
||||||
|
|
||||||
|
**輸入**
|
||||||
|
|
||||||
|
- 種子詞:`敏感肌`
|
||||||
|
- product_brief:溫和修護、無香料、適合敏感/屏障受損肌
|
||||||
|
|
||||||
|
**Brave knowledge_expand(節錄)**
|
||||||
|
|
||||||
|
| 查詢 | snippet 線索 | 圖譜節點 |
|
||||||
|
|------|--------------|----------|
|
||||||
|
| `敏感肌 常見原因` | 屏障受損、過度清潔 | L1 symptom `屏障受損` |
|
||||||
|
| `敏感肌 懷孕` | 荷爾蒙、嗅覺/皮膚變敏感 | L2 cause `懷孕嗅覺敏感` |
|
||||||
|
| `換季 皮膚 泛紅` | 季節性刺激 | L1 symptom `換季泛紅` |
|
||||||
|
|
||||||
|
**衍生 tag(候選,勾選前不 crawl)**
|
||||||
|
|
||||||
|
| 節點 | relevanceQuery | recencyQuery | productFit |
|
||||||
|
|------|----------------|--------------|------------|
|
||||||
|
| 敏感肌 | `敏感肌` | `敏感肌 請問` | 95 |
|
||||||
|
| 屏障受損 | `屏障受損` | `屏障受損 推薦` | 90 |
|
||||||
|
| 換季泛紅 | `換季泛紅` | `換季泛紅 請問` | 88 |
|
||||||
|
| 懷孕皮膚癢 | `懷孕皮膚癢` | `懷孕 皮膚 癢 請益` | 視產品而定 |
|
||||||
|
|
||||||
|
**使用者**:勾選 productFit 高的 4 個節點(可不勾懷孕若產品不適用)
|
||||||
|
|
||||||
|
**startScan**:每個勾選節點的 relevance + recency 詞都跑雙軌
|
||||||
|
|
||||||
|
| 軌道 | 行為 | 本例預期 |
|
||||||
|
|------|------|----------|
|
||||||
|
| 相關軌 | sort=relevance, limit≈12 | 高互動痛點貼文 |
|
||||||
|
| 近期軌 | 7d 優先,不足補 30d | 一週內求助帖 |
|
||||||
|
|
||||||
|
**合併** → gold / recent / relevant → `productFitScore` → 獲客台 → 島民 `generateOutreachReply` + fill
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 近期窗口
|
||||||
|
|
||||||
|
| 窗口 | 天數 | 行為 |
|
||||||
|
|------|------|------|
|
||||||
|
| **重點** | 7 天內 | 優先爬取、優先顯示、排序最高 |
|
||||||
|
| **補充** | 8~30 天 | 7 天內不足時才補,排序較低 |
|
||||||
|
| **排除** | >30 天 | 不進海巡與獲客清單 |
|
||||||
|
|
||||||
|
策略:
|
||||||
|
|
||||||
|
1. 每個勾選 tag 的**近期軌**先抓滿 7 天名額
|
||||||
|
2. 全輪痛點貼文不足目標時,自動放寬至 30 天
|
||||||
|
3. 獲客台預設篩「7 天內」,可切「含 30 天內補充」
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 雙軌海巡(Tag + Crawl + UI 三層對齊)
|
||||||
|
|
||||||
|
**近期軌不是相關軌的副產品**——每個勾選 tag 的 relevance 與 recency 查詢都**必跑**。
|
||||||
|
|
||||||
|
| 層級 | 相關 | 近期 |
|
||||||
|
|------|------|------|
|
||||||
|
| **Tag** | `derivedTags.relevance` 短詞高命中 | `derivedTags.recency` 求助語境 + after 日期 |
|
||||||
|
| **Crawl** | 相關軌 sort=relevance, limit≈12 | 近期軌 7d 滿額 → 30d 補 |
|
||||||
|
| **UI** | 可篩 `priority=relevant` | 預設 7d + `priority=gold` 置頂 |
|
||||||
|
|
||||||
|
合併優先級:
|
||||||
|
|
||||||
|
1. 兩軌皆有 → `gold`
|
||||||
|
2. 僅近期軌 → `recent`
|
||||||
|
3. 僅相關軌 → `relevant`
|
||||||
|
|
||||||
|
過濾:移植 `hasPlacementIntent`、`looksLikeCasualChat`(舊 [`lib/topic-anchor.ts`](../../lib/topic-anchor.ts)、[`lib/scan-recency.ts`](../../lib/scan-recency.ts))。
|
||||||
|
|
||||||
|
### scan_posts 擴充欄位
|
||||||
|
|
||||||
|
- `placement_score`、`priority`(gold/recent/relevant)
|
||||||
|
- `product_fit_score`、`solved_by_product`
|
||||||
|
- `posted_at`、`search_tag`、`query_dimension`(relevance/recency)
|
||||||
|
- `graph_node_id`
|
||||||
|
- `replies[]`(可選,`scrape_replies: true`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 產品匹配驗收
|
||||||
|
|
||||||
|
每篇海巡結果:
|
||||||
|
|
||||||
|
- **`productFitScore`**:痛點 vs `product_brief`
|
||||||
|
- **`solvedByProduct`**:獲客留言是否對應產品能力(生成時強制檢查)
|
||||||
|
|
||||||
|
獲客台 UI:
|
||||||
|
|
||||||
|
- 預設排序:7 天內 + 產品能解決
|
||||||
|
- 標示:可置入 / 需人工 / 超出產品範圍
|
||||||
|
- 獲客留言:**島民 fill 全文,不自動送出**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 島民交接
|
||||||
|
|
||||||
|
### job.result.handoff
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"handoff": {
|
||||||
|
"flow": "placement",
|
||||||
|
"persona_id": "...",
|
||||||
|
"pain_tag_count": 9,
|
||||||
|
"summary": "12 候選 tag → 勾選 6 節點 → 38 篇;痛點 10(核心 6 + 周邊 4);7 天內 8 篇",
|
||||||
|
"pain_breakdown": { "core": 6, "peripheral": 4, "recent_7d": 8 },
|
||||||
|
"top_peripheral_hits": ["懷孕皮膚癢", "換季泛紅"],
|
||||||
|
"next_route": "/personas/:id/outreach",
|
||||||
|
"needs_supplemental_expand": false,
|
||||||
|
"connection_required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
JobMonitor → `islanderHandoffStore`;[`buildIslanderContext`](../web/src/lib/islander/buildIslanderContext.ts) 注入【近期海巡交接】。
|
||||||
|
|
||||||
|
### Custom actions
|
||||||
|
|
||||||
|
| Action | 用途 |
|
||||||
|
|--------|------|
|
||||||
|
| `expandKnowledgeGraph` | 觸發 `expand-graph`;`supplemental=true` 補充迴圈 |
|
||||||
|
| `toggleGraphNode` | 勾選/取消節點 |
|
||||||
|
| `startScan` | `dual_track=true`,只爬 `selectedForScan` 節點 |
|
||||||
|
| `generateOutreachReply` | 產獲客留言 |
|
||||||
|
| `applyDraft` | fill 留言欄位 |
|
||||||
|
|
||||||
|
### 對話路徑(流程 B)
|
||||||
|
|
||||||
|
```text
|
||||||
|
「幫我找敏感肌的痛點」
|
||||||
|
→ expand-graph(Brave knowledge_expand + AI 合成 TKG)
|
||||||
|
→ IF pain_tag_count < 8 → 島民:「要再補一輪 Brave 嗎?」→ supplemental_round
|
||||||
|
→ 研究頁:圖譜 + 雙維度 tag + productFitScore
|
||||||
|
→ 使用者手動勾選節點
|
||||||
|
→ startScan(每詞雙軌:相關 + 近期,7d/30d)
|
||||||
|
→ outreach → highlight gold/recent → generateOutreachReply + fill
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Job 模板
|
||||||
|
|
||||||
|
| Template | Steps | worker |
|
||||||
|
|----------|-------|--------|
|
||||||
|
| `expand-graph` | plan_queries → brave_knowledge → ai_synth → derive_tags → [supplemental?] → persist_tkg | go |
|
||||||
|
| `scan` | session → crawl_dual_track → replies? → store → filter → ai_fit → persist | node + go |
|
||||||
|
| `style-8d` | (既有) | node + go |
|
||||||
|
|
||||||
|
執行順序:**expand-graph → 勾選 tag → scan**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API 草案
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /api/v1/personas/:id/knowledge-graph/expand # ?supplemental=true
|
||||||
|
GET /api/v1/personas/:id/knowledge-graph
|
||||||
|
PATCH /api/v1/personas/:id/knowledge-graph/nodes # selectedForScan
|
||||||
|
|
||||||
|
POST /api/v1/personas/:id/scan-jobs # graph_id, selected_node_ids, dual_track
|
||||||
|
GET /api/v1/personas/:id/scan-posts # recent_7d, product_fit_min, priority
|
||||||
|
|
||||||
|
POST /api/v1/personas/:id/outreach-drafts/generate
|
||||||
|
```
|
||||||
|
|
||||||
|
Internal worker:`POST /workers/scan-posts/batch`、Brave/AI 內部端點。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 前端頁面
|
||||||
|
|
||||||
|
| 路徑 | 用途 | 島民 label |
|
||||||
|
|------|------|------------|
|
||||||
|
| `/personas/:id/research` | 圖譜、雙維度 tag、勾選 | 加入海巡、Brave 再擴展 |
|
||||||
|
| `/personas/:id/outreach` | 獲客貼文 + 留言 | 獲客留言、標記已處理 |
|
||||||
|
| `/personas/:id/matrix` | 流程 A(Phase 2) | 草稿內容 |
|
||||||
|
|
||||||
|
研究頁每節點展示:`relevanceQueries`、`recencyQueries`、`productFitScore`、勾選框、上次命中數。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 實作分期
|
||||||
|
|
||||||
|
### Phase 0a — 知識圖譜 + Tag 流水線
|
||||||
|
|
||||||
|
- [ ] Go Brave adapter(`knowledge_expand` / `threads_discover`)
|
||||||
|
- [ ] `expand-graph` job:plan_queries → brave → ai_synth → derive_tags
|
||||||
|
- [ ] `supplemental_round`(痛點 tag < 8)
|
||||||
|
- [ ] Mongo `topic_knowledge_graphs`(含 `derivedTags`、`painTagCount`)
|
||||||
|
- [ ] `deriveSearchTagsFromGraph`(relevance + recency 雙陣列)
|
||||||
|
- [ ] API expand / get / patch nodes
|
||||||
|
|
||||||
|
### Phase 0b — 島民 handoff
|
||||||
|
|
||||||
|
- [ ] handoff(含 `pain_tag_count`、`needs_supplemental_expand`)
|
||||||
|
- [ ] JobMonitor bridge
|
||||||
|
- [ ] custom actions + `ai.islander.system.md` 海巡專章
|
||||||
|
|
||||||
|
### Phase 1 — 雙軌 scan + 流程 B
|
||||||
|
|
||||||
|
- [ ] Node `crawl_dual_track`(每 tag 相關+近期,7d/30d)
|
||||||
|
- [ ] `productFitScore` + outreach UI
|
||||||
|
- [ ] **驗收**:敏感肌 → L2(懷孕等)→ 候選痛點 tag ≥8 → 勾選後貼文痛點 ≥8 → 7d 內 ≥5
|
||||||
|
|
||||||
|
### Phase 2 — 流程 A
|
||||||
|
|
||||||
|
- [ ] matrix + 留言收集 + 島民 fill
|
||||||
|
|
||||||
|
### Phase 3 — 自動化
|
||||||
|
|
||||||
|
- [ ] job_schedules、Brave 熔斷、Meta API 發留言
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 風險
|
||||||
|
|
||||||
|
| 議題 | 對策 |
|
||||||
|
|------|------|
|
||||||
|
| Brave 幻覺 | 節點必須有 `evidence[]` |
|
||||||
|
| 圖譜跑題 | exclusions + `productFitScore` |
|
||||||
|
| 查詢爆炸 | Brave ≤15/輪;supplemental ≤5;衍生 ≤20;只爬勾選 |
|
||||||
|
| 醫療敏感 | `disclaimer`;留言不自動發 |
|
||||||
|
| 周邊節點產品不符 | 低 productFit 預設不勾;獲客台標 ✗ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 參考
|
||||||
|
|
||||||
|
- 舊海巡:[`lib/services/scan.ts`](../../lib/services/scan.ts)
|
||||||
|
- 舊網搜:[`lib/services/scan-web-discover.ts`](../../lib/services/scan-web-discover.ts)
|
||||||
|
- 舊研究地圖:[`lib/ai/analyze-topic.ts`](../../lib/ai/analyze-topic.ts)
|
||||||
|
- Job 系統:[`docs/job-system-plan.md`](./job-system-plan.md)
|
||||||
|
- 島民:[`internal/library/prompt/files/ai.islander.system.md`](../internal/library/prompt/files/ai.islander.system.md)
|
||||||
|
- 既有 8D:[`worker/style-8d-worker.ts`](../worker/style-8d-worker.ts)
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
# Threads 自動營運補功能計畫
|
|
||||||
|
|
||||||
## 目前狀態
|
|
||||||
|
|
||||||
P1/P2 已落地到後端 API、worker 與正式前端:
|
|
||||||
|
|
||||||
- 自動補庫存:`publish_inventory_policies` + `refill-publish-inventory` job template/worker。
|
|
||||||
- 智慧時段:`GET /publish-slot-insights` 從 publish health / checkpoints 聚合推薦時段。
|
|
||||||
- 草稿批量排程:Persona 與 Copy Mission 草稿可批量排到 `publish_queue`,草稿回寫 `scheduled + publish_queue_id`。
|
|
||||||
- Retry / 告警 / 漏發:`publish_queue_events` 記錄狀態轉移;sweeper 會標記 missed;failed/cancelled/missed 可 retry。
|
|
||||||
- 頻率護欄:`publish_guard_policies` 支援每日上限、最小間隔、連續失敗 pause / resume。
|
|
||||||
- P2:內容日曆 date range query、多帳號 dashboard summary、style preset 語調庫、OAuth/API diagnostics。
|
|
||||||
|
|
||||||
## 目標
|
|
||||||
|
|
||||||
把現有「海巡找題、AI 文案、手動排程、成效追蹤」串成自動營運閉環,讓巡樓 Console 不只是一個排程工具,而是可持續補庫存、控制風險、追蹤成效的 Threads 工作台。
|
|
||||||
|
|
||||||
## P1:自動補庫存
|
|
||||||
|
|
||||||
### 功能
|
|
||||||
|
|
||||||
- 每個 Threads account 可設定每日/每週目標篇數。
|
|
||||||
- 可設定 weekday/time slots 與 timezone;timezone 僅用於 cron/排程解讀,儲存仍用 unix nanoseconds UTC。
|
|
||||||
- 指定內容來源:persona、copy mission、brand scan posts、manual seed。
|
|
||||||
- 當 `publish_queue` 未來 N 天庫存低於門檻時,自動建立 copy generation job,完成後排入 queue。
|
|
||||||
|
|
||||||
### 後端草案
|
|
||||||
|
|
||||||
- 新增 model:`internal/model/publish_inventory/`。
|
|
||||||
- 新增設定 entity:
|
|
||||||
- `account_id`
|
|
||||||
- `target_daily_count`
|
|
||||||
- `low_stock_threshold`
|
|
||||||
- `lookahead_days`
|
|
||||||
- `timezone`
|
|
||||||
- `slots`
|
|
||||||
- `source_refs`
|
|
||||||
- `enabled`
|
|
||||||
- 新增 job step:`refill_publish_inventory`。
|
|
||||||
- 補 API:
|
|
||||||
- `GET /api/v1/threads-accounts/:accountId/publish-inventory-policy`
|
|
||||||
- `PUT /api/v1/threads-accounts/:accountId/publish-inventory-policy`
|
|
||||||
- `POST /api/v1/threads-accounts/:accountId/publish-inventory/refill-jobs`
|
|
||||||
|
|
||||||
## P1:智慧時段
|
|
||||||
|
|
||||||
### 功能
|
|
||||||
|
|
||||||
- 第一版以規則為主:帳號的 weekday/time slots + 最小間隔。
|
|
||||||
- 用 publish health 的 1h/24h/7d checkpoints 回填每個 slot 的平均互動。
|
|
||||||
- 前端顯示「推薦時段」與「低成效時段」。
|
|
||||||
|
|
||||||
### 後端草案
|
|
||||||
|
|
||||||
- 新增 summary usecase,從 `publish_analytics` 聚合 account slot performance。
|
|
||||||
- 補 API:
|
|
||||||
- `GET /api/v1/threads-accounts/:accountId/publish-slot-insights`
|
|
||||||
- 回傳:
|
|
||||||
- `slots[]`
|
|
||||||
- `avg_likes_1h/24h/7d`
|
|
||||||
- `avg_replies_1h/24h/7d`
|
|
||||||
- `sample_size`
|
|
||||||
- `recommendation`
|
|
||||||
|
|
||||||
## P1:草稿批量排程
|
|
||||||
|
|
||||||
### 功能
|
|
||||||
|
|
||||||
- 使用者在 copy drafts 中選多篇,一次排到未來 slots。
|
|
||||||
- 支援根據智慧時段自動分配 `scheduled_at`。
|
|
||||||
- draft 排入 queue 後狀態改為 `scheduled`,保留 queue item id。
|
|
||||||
|
|
||||||
### 後端草案
|
|
||||||
|
|
||||||
- 補 API:
|
|
||||||
- `POST /api/v1/personas/:personaId/copy-drafts/schedule`
|
|
||||||
- `POST /api/v1/personas/:personaId/copy-missions/:missionId/copy-drafts/schedule`
|
|
||||||
- request:
|
|
||||||
- `account_id`
|
|
||||||
- `draft_ids`
|
|
||||||
- `start_at`
|
|
||||||
- `timezone`
|
|
||||||
- `slots`
|
|
||||||
- `mode`: `manual` / `recommended`
|
|
||||||
|
|
||||||
## P1:發文 retry / 告警 / 漏發偵測
|
|
||||||
|
|
||||||
### 功能
|
|
||||||
|
|
||||||
- failed queue item 可重試,保留失敗歷史。
|
|
||||||
- sweeper 偵測到超過 grace period 的 scheduled item 未處理,標記為 missed 或重新 enqueue。
|
|
||||||
- 連續失敗超過門檻時 pause account publish,避免 token 或 rate limit 問題擴大。
|
|
||||||
|
|
||||||
### 後端草案
|
|
||||||
|
|
||||||
- `publish_queue_events` 記錄 queue 狀態轉移與錯誤分類。
|
|
||||||
- `retry` 使用 guarded update,只允許 `failed/cancelled` 回到 `scheduled`。
|
|
||||||
- 補 API:
|
|
||||||
- `POST /api/v1/threads-accounts/:accountId/publish-queue/:queueId/retry`
|
|
||||||
- `GET /api/v1/threads-accounts/:accountId/publish-queue/:queueId/events`
|
|
||||||
- `GET /api/v1/threads-accounts/:accountId/publish-alerts`
|
|
||||||
|
|
||||||
## P1:頻率護欄
|
|
||||||
|
|
||||||
### 功能
|
|
||||||
|
|
||||||
- 每帳號每日上限。
|
|
||||||
- 最小發文間隔。
|
|
||||||
- 連續失敗自動 pause。
|
|
||||||
- 手動解除 pause。
|
|
||||||
|
|
||||||
### 後端草案
|
|
||||||
|
|
||||||
- 擴充 Threads account connection prefs 或新增 `publish_guard_policy`。
|
|
||||||
- sweeper dispatch 前檢查 policy;違反則延後排程,不直接發文。
|
|
||||||
- 補 API:
|
|
||||||
- `GET /api/v1/threads-accounts/:accountId/publish-guard-policy`
|
|
||||||
- `PUT /api/v1/threads-accounts/:accountId/publish-guard-policy`
|
|
||||||
- `POST /api/v1/threads-accounts/:accountId/publish-guard/resume`
|
|
||||||
|
|
||||||
## P2
|
|
||||||
|
|
||||||
- 內容日曆:補 queue date range query,前端做週/月視圖。
|
|
||||||
- 多帳號 dashboard:補跨帳號 summary endpoint,避免前端 N+1 requests。
|
|
||||||
- 語調庫:把 `style_profile`、CTA、禁用詞、品牌口吻抽成 reusable preset。
|
|
||||||
- OAuth/API 診斷:把 smoke test、token expiry、permission scope 做成標準診斷報告。
|
|
||||||
|
|
||||||
## 驗證策略
|
|
||||||
|
|
||||||
- 所有列表維持 `page/pageSize` 與 `pagination/list`。
|
|
||||||
- 所有時間欄位寫入 unix nanoseconds。
|
|
||||||
- queue/job 狀態轉移都使用 guarded update。
|
|
||||||
- 長任務必須 heartbeat,取消走既有 cooperative cancel 語意。
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
Name: haixun-backend
|
||||||
|
Host: 0.0.0.0
|
||||||
|
Port: 8890
|
||||||
|
Timeout: 120000
|
||||||
|
|
||||||
|
Mongo:
|
||||||
|
URI: mongodb://127.0.0.1:27017
|
||||||
|
Database: haixun_dev
|
||||||
|
TimeoutSeconds: 10
|
||||||
|
|
||||||
|
Redis:
|
||||||
|
Addr: 127.0.0.1:6379
|
||||||
|
DB: 0
|
||||||
|
|
||||||
|
Auth:
|
||||||
|
AccessSecret: haixun-dev-access-secret-change-me
|
||||||
|
RefreshSecret: haixun-dev-refresh-secret-change-me
|
||||||
|
AccessExpireSeconds: 900
|
||||||
|
RefreshExpireSeconds: 2592000
|
||||||
|
DevHeaderFallback: true
|
||||||
|
|
@ -3,6 +3,8 @@ Host: 0.0.0.0
|
||||||
Port: 8890
|
Port: 8890
|
||||||
Timeout: 120000
|
Timeout: 120000
|
||||||
|
|
||||||
|
# 連線字串與所有 secret 都從環境變數注入(systemd EnvironmentFile=/opt/haixun/etc/haixun.env)。
|
||||||
|
# go-zero 以 conf.UseEnv() + os.ExpandEnv 展開 ${VAR};未設定的變數會展開為空字串並讓服務 fail fast。
|
||||||
Mongo:
|
Mongo:
|
||||||
URI: ${HAIXUN_MONGO_URI}
|
URI: ${HAIXUN_MONGO_URI}
|
||||||
Database: ${HAIXUN_MONGO_DB}
|
Database: ${HAIXUN_MONGO_DB}
|
||||||
|
|
@ -28,28 +30,12 @@ InternalWorker:
|
||||||
|
|
||||||
JobWorker:
|
JobWorker:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
WorkerType: disabled
|
WorkerType: go
|
||||||
|
|
||||||
JobScheduler:
|
JobScheduler:
|
||||||
Enabled: false
|
Enabled: true
|
||||||
IntervalSeconds: 60
|
IntervalSeconds: 60
|
||||||
|
|
||||||
JobReaper:
|
JobReaper:
|
||||||
Enabled: false
|
Enabled: true
|
||||||
IntervalSeconds: 30
|
IntervalSeconds: 30
|
||||||
|
|
||||||
ThreadsOAuth:
|
|
||||||
AppID: "${THREADS_APP_ID}"
|
|
||||||
AppSecret: "${THREADS_APP_SECRET}"
|
|
||||||
RedirectURI: "${THREADS_REDIRECT_URI}"
|
|
||||||
SuccessRedirect: "${THREADS_OAUTH_SUCCESS_REDIRECT}"
|
|
||||||
|
|
||||||
ThreadsTokenRefresh:
|
|
||||||
Enabled: false
|
|
||||||
IntervalSeconds: 3600
|
|
||||||
LeadDays: 7
|
|
||||||
|
|
||||||
ThreadsPublishQueue:
|
|
||||||
Enabled: false
|
|
||||||
IntervalSeconds: 60
|
|
||||||
BatchSize: 20
|
|
||||||
|
|
|
||||||
|
|
@ -27,30 +27,13 @@ InternalWorker:
|
||||||
Secret: ${HAIXUN_WORKER_SECRET}
|
Secret: ${HAIXUN_WORKER_SECRET}
|
||||||
|
|
||||||
JobWorker:
|
JobWorker:
|
||||||
Enabled: ${HAIXUN_JOB_WORKER_ENABLED}
|
Enabled: true
|
||||||
WorkerType: ${HAIXUN_JOB_WORKER_TYPE}
|
WorkerType: go
|
||||||
WorkerID: ${HAIXUN_JOB_WORKER_ID}
|
|
||||||
|
|
||||||
JobScheduler:
|
JobScheduler:
|
||||||
Enabled: ${HAIXUN_JOB_SCHEDULER_ENABLED}
|
Enabled: false
|
||||||
IntervalSeconds: ${HAIXUN_JOB_SCHEDULER_INTERVAL_SECONDS}
|
IntervalSeconds: 60
|
||||||
|
|
||||||
JobReaper:
|
JobReaper:
|
||||||
Enabled: ${HAIXUN_JOB_REAPER_ENABLED}
|
Enabled: false
|
||||||
IntervalSeconds: ${HAIXUN_JOB_REAPER_INTERVAL_SECONDS}
|
IntervalSeconds: 30
|
||||||
|
|
||||||
ThreadsOAuth:
|
|
||||||
AppID: "${THREADS_APP_ID}"
|
|
||||||
AppSecret: "${THREADS_APP_SECRET}"
|
|
||||||
RedirectURI: "${THREADS_REDIRECT_URI}"
|
|
||||||
SuccessRedirect: "${THREADS_OAUTH_SUCCESS_REDIRECT}"
|
|
||||||
|
|
||||||
ThreadsTokenRefresh:
|
|
||||||
Enabled: ${HAIXUN_THREADS_TOKEN_REFRESH_ENABLED}
|
|
||||||
IntervalSeconds: ${HAIXUN_THREADS_TOKEN_REFRESH_INTERVAL_SECONDS}
|
|
||||||
LeadDays: ${HAIXUN_THREADS_TOKEN_REFRESH_LEAD_DAYS}
|
|
||||||
|
|
||||||
ThreadsPublishQueue:
|
|
||||||
Enabled: ${HAIXUN_THREADS_PUBLISH_QUEUE_ENABLED}
|
|
||||||
IntervalSeconds: ${HAIXUN_THREADS_PUBLISH_QUEUE_INTERVAL_SECONDS}
|
|
||||||
BatchSize: ${HAIXUN_THREADS_PUBLISH_QUEUE_BATCH_SIZE}
|
|
||||||
|
|
|
||||||
|
|
@ -5,32 +5,31 @@ Timeout: 120000
|
||||||
|
|
||||||
# 本機開發 worker 設定(go worker)。搭配 `make dev-infra` 的 Mongo/Redis。
|
# 本機開發 worker 設定(go worker)。搭配 `make dev-infra` 的 Mongo/Redis。
|
||||||
Mongo:
|
Mongo:
|
||||||
URI: ${HAIXUN_MONGO_URI}
|
URI: mongodb://haixun:change-me-mongo-pass@127.0.0.1:27017/?authSource=admin
|
||||||
Database: ${HAIXUN_MONGO_DB}
|
Database: haixun
|
||||||
TimeoutSeconds: 10
|
TimeoutSeconds: 10
|
||||||
|
|
||||||
Redis:
|
Redis:
|
||||||
Addr: ${HAIXUN_REDIS_ADDR}
|
Addr: 127.0.0.1:6379
|
||||||
Password: ${HAIXUN_REDIS_PASSWORD}
|
Password: change-me-redis-pass
|
||||||
DB: 0
|
DB: 0
|
||||||
|
|
||||||
Auth:
|
Auth:
|
||||||
AccessSecret: ${HAIXUN_JWT_ACCESS_SECRET}
|
AccessSecret: haixun-dev-access-secret-change-me
|
||||||
RefreshSecret: ${HAIXUN_JWT_REFRESH_SECRET}
|
RefreshSecret: haixun-dev-refresh-secret-change-me
|
||||||
AccessExpireSeconds: 900
|
AccessExpireSeconds: 900
|
||||||
RefreshExpireSeconds: 2592000
|
RefreshExpireSeconds: 2592000
|
||||||
DevHeaderFallback: false
|
DevHeaderFallback: false
|
||||||
|
|
||||||
Secrets:
|
Secrets:
|
||||||
EncryptionKey: ${HAIXUN_SECRETS_KEY}
|
EncryptionKey: ""
|
||||||
|
|
||||||
InternalWorker:
|
InternalWorker:
|
||||||
Secret: ${HAIXUN_WORKER_SECRET}
|
Secret: haixun-dev-worker-secret
|
||||||
|
|
||||||
JobWorker:
|
JobWorker:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
WorkerType: ${HAIXUN_JOB_WORKER_TYPE}
|
WorkerType: go
|
||||||
WorkerID: ${HAIXUN_JOB_WORKER_ID}
|
|
||||||
|
|
||||||
JobScheduler:
|
JobScheduler:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
@ -39,13 +38,3 @@ JobScheduler:
|
||||||
JobReaper:
|
JobReaper:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
IntervalSeconds: 30
|
IntervalSeconds: 30
|
||||||
|
|
||||||
ThreadsTokenRefresh:
|
|
||||||
Enabled: false
|
|
||||||
IntervalSeconds: 3600
|
|
||||||
LeadDays: 7
|
|
||||||
|
|
||||||
ThreadsPublishQueue:
|
|
||||||
Enabled: false
|
|
||||||
IntervalSeconds: 60
|
|
||||||
BatchSize: 20
|
|
||||||
|
|
|
||||||
|
|
@ -3,35 +3,36 @@ Host: 0.0.0.0
|
||||||
Port: 8890
|
Port: 8890
|
||||||
Timeout: 120000
|
Timeout: 120000
|
||||||
|
|
||||||
# 本機開發設定。預設搭配 `make dev-infra`(infra/docker-compose.yml,讀 deploy/.env.dev)跑的 Mongo/Redis。
|
# 本機開發設定。預設搭配 `make dev-infra`(infra/docker-compose.yml)跑的 Mongo/Redis,
|
||||||
# 帳密對應 deploy/.env.dev 的值;若改 .env.dev 密碼,這裡不需動(${} 會重讀)。
|
# 帳密對應 infra/.env.example 的預設值。若你改了 .env 密碼,這裡也要同步。
|
||||||
Mongo:
|
Mongo:
|
||||||
URI: ${HAIXUN_MONGO_URI}
|
URI: mongodb://haixun:change-me-mongo-pass@127.0.0.1:27017/?authSource=admin
|
||||||
Database: ${HAIXUN_MONGO_DB}
|
Database: haixun
|
||||||
TimeoutSeconds: 10
|
TimeoutSeconds: 10
|
||||||
|
|
||||||
Redis:
|
Redis:
|
||||||
Addr: ${HAIXUN_REDIS_ADDR}
|
Addr: 127.0.0.1:6379
|
||||||
Password: ${HAIXUN_REDIS_PASSWORD}
|
Password: change-me-redis-pass
|
||||||
DB: 0
|
DB: 0
|
||||||
|
|
||||||
Auth:
|
Auth:
|
||||||
AccessSecret: ${HAIXUN_JWT_ACCESS_SECRET}
|
AccessSecret: haixun-dev-access-secret-change-me
|
||||||
RefreshSecret: ${HAIXUN_JWT_REFRESH_SECRET}
|
RefreshSecret: haixun-dev-refresh-secret-change-me
|
||||||
AccessExpireSeconds: 900
|
AccessExpireSeconds: 900
|
||||||
RefreshExpireSeconds: 2592000
|
RefreshExpireSeconds: 2592000
|
||||||
# 僅本機開發開啟:允許用 X-Tenant-ID / X-UID header 模擬登入。正式環境必須為 false。
|
# 僅本機開發開啟:允許用 X-Tenant-ID / X-UID header 模擬登入。正式環境必須為 false。
|
||||||
DevHeaderFallback: true
|
DevHeaderFallback: true
|
||||||
|
|
||||||
Secrets:
|
Secrets:
|
||||||
EncryptionKey: ${HAIXUN_SECRETS_KEY}
|
# 留空 = 不加密(本機開發方便)。正式環境用 ${HAIXUN_SECRETS_KEY}。
|
||||||
|
EncryptionKey: ""
|
||||||
|
|
||||||
InternalWorker:
|
InternalWorker:
|
||||||
Secret: ${HAIXUN_WORKER_SECRET}
|
Secret: haixun-dev-worker-secret
|
||||||
|
|
||||||
JobWorker:
|
JobWorker:
|
||||||
Enabled: false
|
Enabled: true
|
||||||
WorkerType: disabled
|
WorkerType: go
|
||||||
|
|
||||||
JobScheduler:
|
JobScheduler:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|
@ -40,23 +41,3 @@ JobScheduler:
|
||||||
JobReaper:
|
JobReaper:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
IntervalSeconds: 30
|
IntervalSeconds: 30
|
||||||
|
|
||||||
# Meta Threads OAuth(App Dashboard → 用戶端 OAuth 設定須登記 Redirect URI)
|
|
||||||
ThreadsOAuth:
|
|
||||||
AppID: "${THREADS_APP_ID}"
|
|
||||||
AppSecret: "${THREADS_APP_SECRET}"
|
|
||||||
RedirectURI: "${THREADS_REDIRECT_URI}"
|
|
||||||
# OAuth 完成後瀏覽器導回(現在指向 https://threads-tool.30cm.net )
|
|
||||||
SuccessRedirect: "${THREADS_OAUTH_SUCCESS_REDIRECT}"
|
|
||||||
|
|
||||||
# 系統任務:在 token 到期前自動 enqueue refresh-threads-token job
|
|
||||||
ThreadsTokenRefresh:
|
|
||||||
Enabled: true
|
|
||||||
IntervalSeconds: 3600
|
|
||||||
LeadDays: 7
|
|
||||||
|
|
||||||
# 排程發文佇列 sweeper(掃描 scheduled_at 到期項目並 PublishText)
|
|
||||||
ThreadsPublishQueue:
|
|
||||||
Enabled: true
|
|
||||||
IntervalSeconds: 60
|
|
||||||
BatchSize: 20
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"haixun-backend/internal/bootstrap"
|
|
||||||
"haixun-backend/internal/config"
|
"haixun-backend/internal/config"
|
||||||
"haixun-backend/internal/handler"
|
"haixun-backend/internal/handler"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
|
|
@ -19,8 +18,6 @@ var configFile = flag.String("f", "etc/gateway.yaml", "config file")
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
bootstrap.LoadInfraEnv()
|
|
||||||
|
|
||||||
var c config.Config
|
var c config.Config
|
||||||
conf.MustLoad(*configFile, &c, conf.UseEnv())
|
conf.MustLoad(*configFile, &c, conf.UseEnv())
|
||||||
|
|
||||||
|
|
@ -31,7 +28,6 @@ func main() {
|
||||||
defer sc.Close(context.Background())
|
defer sc.Close(context.Background())
|
||||||
|
|
||||||
handler.RegisterHandlers(server, sc)
|
handler.RegisterHandlers(server, sc)
|
||||||
handler.RegisterExtraHandlers(server, sc)
|
|
||||||
|
|
||||||
fmt.Printf("Starting backend backend at %s:%d...\n", c.Host, c.Port)
|
fmt.Printf("Starting backend backend at %s:%d...\n", c.Host, c.Port)
|
||||||
server.Start()
|
server.Start()
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ type (
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
ProductContext string `json:"product_context"`
|
ProductContext string `json:"product_context"`
|
||||||
PlacementURL string `json:"placement_url,omitempty"`
|
|
||||||
MatchTags []string `json:"match_tags,omitempty"`
|
MatchTags []string `json:"match_tags,omitempty"`
|
||||||
CreateAt int64 `json:"create_at"`
|
CreateAt int64 `json:"create_at"`
|
||||||
UpdateAt int64 `json:"update_at"`
|
UpdateAt int64 `json:"update_at"`
|
||||||
|
|
@ -66,14 +65,12 @@ type (
|
||||||
CreateBrandProductReq {
|
CreateBrandProductReq {
|
||||||
Label string `json:"label" validate:"required"`
|
Label string `json:"label" validate:"required"`
|
||||||
ProductContext string `json:"product_context" validate:"required"`
|
ProductContext string `json:"product_context" validate:"required"`
|
||||||
PlacementURL string `json:"placement_url,optional"`
|
|
||||||
MatchTags []string `json:"match_tags,optional"`
|
MatchTags []string `json:"match_tags,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateBrandProductReq {
|
UpdateBrandProductReq {
|
||||||
Label *string `json:"label,optional"`
|
Label *string `json:"label,optional"`
|
||||||
ProductContext *string `json:"product_context,optional"`
|
ProductContext *string `json:"product_context,optional"`
|
||||||
PlacementURL string `json:"placement_url,optional"`
|
|
||||||
MatchTags []string `json:"match_tags,optional"`
|
MatchTags []string `json:"match_tags,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,7 +183,6 @@ type (
|
||||||
NodeIDs []string `json:"node_ids,optional"`
|
NodeIDs []string `json:"node_ids,optional"`
|
||||||
DualTrack bool `json:"dual_track,optional"`
|
DualTrack bool `json:"dual_track,optional"`
|
||||||
PatrolMode bool `json:"patrol_mode,optional"`
|
PatrolMode bool `json:"patrol_mode,optional"`
|
||||||
TestPatrol bool `json:"test_patrol,optional"`
|
|
||||||
PatrolKeywords []string `json:"patrol_keywords,optional"`
|
PatrolKeywords []string `json:"patrol_keywords,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,7 +219,6 @@ type (
|
||||||
PublishedPermalink string `json:"published_permalink,omitempty"`
|
PublishedPermalink string `json:"published_permalink,omitempty"`
|
||||||
OutreachUpdateAt int64 `json:"outreach_update_at,omitempty"`
|
OutreachUpdateAt int64 `json:"outreach_update_at,omitempty"`
|
||||||
PostedAt string `json:"posted_at,omitempty"`
|
PostedAt string `json:"posted_at,omitempty"`
|
||||||
RecencyDays int `json:"recency_days,omitempty"`
|
|
||||||
Replies []ScanReplyData `json:"replies,omitempty"`
|
Replies []ScanReplyData `json:"replies,omitempty"`
|
||||||
LatestDraft *GenerateOutreachDraftsData `json:"latest_draft,omitempty"`
|
LatestDraft *GenerateOutreachDraftsData `json:"latest_draft,omitempty"`
|
||||||
CreateAt int64 `json:"create_at"`
|
CreateAt int64 `json:"create_at"`
|
||||||
|
|
@ -240,7 +235,6 @@ type (
|
||||||
Count int `json:"count,optional"`
|
Count int `json:"count,optional"`
|
||||||
VoicePersonaID string `json:"voice_persona_id,optional"`
|
VoicePersonaID string `json:"voice_persona_id,optional"`
|
||||||
ProductID string `json:"product_id,optional"`
|
ProductID string `json:"product_id,optional"`
|
||||||
Regenerate bool `json:"regenerate,optional"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OutreachDraftItemData {
|
OutreachDraftItemData {
|
||||||
|
|
@ -264,11 +258,6 @@ type (
|
||||||
Confirm bool `json:"confirm"`
|
Confirm bool `json:"confirm"`
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateOutreachDraftReq {
|
|
||||||
DraftIndex int `json:"draft_index"`
|
|
||||||
Text string `json:"text" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishOutreachDraftData {
|
PublishOutreachDraftData {
|
||||||
ScanPostID string `json:"scan_post_id"`
|
ScanPostID string `json:"scan_post_id"`
|
||||||
ReplyID string `json:"reply_id"`
|
ReplyID string `json:"reply_id"`
|
||||||
|
|
@ -366,12 +355,6 @@ type (
|
||||||
PublishOutreachDraftReq
|
PublishOutreachDraftReq
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateOutreachDraftHandlerReq {
|
|
||||||
BrandPath
|
|
||||||
DraftID string `path:"draftId" validate:"required"`
|
|
||||||
UpdateOutreachDraftReq
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchScanPostOutreachHandlerReq {
|
PatchScanPostOutreachHandlerReq {
|
||||||
BrandPath
|
BrandPath
|
||||||
PostID string `path:"postId"`
|
PostID string `path:"postId"`
|
||||||
|
|
@ -445,9 +428,6 @@ service gateway {
|
||||||
@handler publishOutreachDraft
|
@handler publishOutreachDraft
|
||||||
post /:id/outreach-drafts/publish (PublishOutreachDraftHandlerReq) returns (PublishOutreachDraftData)
|
post /:id/outreach-drafts/publish (PublishOutreachDraftHandlerReq) returns (PublishOutreachDraftData)
|
||||||
|
|
||||||
@handler updateOutreachDraft
|
|
||||||
patch /:id/outreach-drafts/:draftId (UpdateOutreachDraftHandlerReq) returns (GenerateOutreachDraftsData)
|
|
||||||
|
|
||||||
@handler patchScanPostOutreach
|
@handler patchScanPostOutreach
|
||||||
patch /:id/scan-posts/:postId (PatchScanPostOutreachHandlerReq) returns (ScanPostData)
|
patch /:id/scan-posts/:postId (PatchScanPostOutreachHandlerReq) returns (ScanPostData)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,39 +9,17 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
CopySimilarAccountData {
|
CopySimilarAccountData {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Reason string `json:"reason,omitempty"`
|
Reason string `json:"reason,omitempty"`
|
||||||
Source string `json:"source,omitempty"`
|
Source string `json:"source,omitempty"`
|
||||||
MatchedSource []string `json:"matched_source,omitempty"`
|
Confidence string `json:"confidence,omitempty"`
|
||||||
Confidence string `json:"confidence,omitempty"`
|
ProfileUrl string `json:"profile_url,omitempty"`
|
||||||
Status string `json:"status,omitempty"`
|
AuthorVerified bool `json:"author_verified,omitempty"`
|
||||||
TopicRelevance float64 `json:"topic_relevance,omitempty"`
|
FollowerCount int `json:"follower_count,omitempty"`
|
||||||
LastSeenAt int64 `json:"last_seen_at,omitempty"`
|
EngagementScore int `json:"engagement_score,omitempty"`
|
||||||
ProfileUrl string `json:"profile_url,omitempty"`
|
LikeCount int `json:"like_count,omitempty"`
|
||||||
AuthorVerified bool `json:"author_verified,omitempty"`
|
ReplyCount int `json:"reply_count,omitempty"`
|
||||||
FollowerCount int `json:"follower_count,omitempty"`
|
PostCount int `json:"post_count,omitempty"`
|
||||||
EngagementScore int `json:"engagement_score,omitempty"`
|
|
||||||
LikeCount int `json:"like_count,omitempty"`
|
|
||||||
ReplyCount int `json:"reply_count,omitempty"`
|
|
||||||
PostCount int `json:"post_count,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CopyAudienceSampleData {
|
|
||||||
Username string `json:"username"`
|
|
||||||
SamplePostId string `json:"sample_post_id,omitempty"`
|
|
||||||
SampleText string `json:"sample_text,omitempty"`
|
|
||||||
ReplyLikeCount int `json:"reply_like_count,omitempty"`
|
|
||||||
Appearances int `json:"appearances,omitempty"`
|
|
||||||
FirstSeenAt int64 `json:"first_seen_at"`
|
|
||||||
LastSeenAt int64 `json:"last_seen_at,omitempty"`
|
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CopyMissionResearchItemData {
|
|
||||||
Title string `json:"title,omitempty"`
|
|
||||||
URL string `json:"url,omitempty"`
|
|
||||||
Snippet string `json:"snippet,omitempty"`
|
|
||||||
Query string `json:"query,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CopyMissionResearchMapData {
|
CopyMissionResearchMapData {
|
||||||
|
|
@ -50,49 +28,11 @@ type (
|
||||||
Questions []string `json:"questions,omitempty"`
|
Questions []string `json:"questions,omitempty"`
|
||||||
Pillars []string `json:"pillars,omitempty"`
|
Pillars []string `json:"pillars,omitempty"`
|
||||||
Exclusions []string `json:"exclusions,omitempty"`
|
Exclusions []string `json:"exclusions,omitempty"`
|
||||||
KnowledgeItems []string `json:"knowledge_items,omitempty"`
|
|
||||||
SelectedKnowledgeItems []string `json:"selected_knowledge_items,omitempty"`
|
|
||||||
ResearchItems []CopyMissionResearchItemData `json:"research_items,omitempty"`
|
|
||||||
SuggestedTags []CopySuggestedTagData `json:"suggested_tags,omitempty"`
|
SuggestedTags []CopySuggestedTagData `json:"suggested_tags,omitempty"`
|
||||||
SimilarAccounts []CopySimilarAccountData `json:"similar_accounts,omitempty"`
|
SimilarAccounts []CopySimilarAccountData `json:"similar_accounts,omitempty"`
|
||||||
AudienceSamples []CopyAudienceSampleData `json:"audience_samples,omitempty"`
|
|
||||||
BenchmarkNotes string `json:"benchmark_notes,omitempty"`
|
BenchmarkNotes string `json:"benchmark_notes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
ExpandCopyMissionGraphHandlerReq {
|
|
||||||
CopyMissionPath
|
|
||||||
ExpandKnowledgeGraphReq
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchCopyMissionGraphNodesHandlerReq {
|
|
||||||
CopyMissionPath
|
|
||||||
PatchKnowledgeGraphNodesReq
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchSimilarAccountReq {
|
|
||||||
Status string `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchAudienceSampleReq {
|
|
||||||
Status string `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CopyMissionSimilarAccountPath {
|
|
||||||
PersonaID string `path:"personaId" validate:"required"`
|
|
||||||
ID string `path:"id" validate:"required"`
|
|
||||||
Username string `path:"username" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchSimilarAccountHandlerReq {
|
|
||||||
CopyMissionSimilarAccountPath
|
|
||||||
PatchSimilarAccountReq
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchAudienceSampleHandlerReq {
|
|
||||||
CopyMissionSimilarAccountPath
|
|
||||||
PatchAudienceSampleReq
|
|
||||||
}
|
|
||||||
|
|
||||||
CopyMissionData {
|
CopyMissionData {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
PersonaID string `json:"persona_id"`
|
PersonaID string `json:"persona_id"`
|
||||||
|
|
@ -126,8 +66,6 @@ type (
|
||||||
Questions []string `json:"questions,optional"`
|
Questions []string `json:"questions,optional"`
|
||||||
Pillars []string `json:"pillars,optional"`
|
Pillars []string `json:"pillars,optional"`
|
||||||
Exclusions []string `json:"exclusions,optional"`
|
Exclusions []string `json:"exclusions,optional"`
|
||||||
KnowledgeItems []string `json:"knowledge_items,optional"`
|
|
||||||
SelectedKnowledgeItems []string `json:"selected_knowledge_items,optional"`
|
|
||||||
BenchmarkNotes *string `json:"benchmark_notes,optional"`
|
BenchmarkNotes *string `json:"benchmark_notes,optional"`
|
||||||
SelectedTags []string `json:"selected_tags,optional"`
|
SelectedTags []string `json:"selected_tags,optional"`
|
||||||
Status *string `json:"status,optional"`
|
Status *string `json:"status,optional"`
|
||||||
|
|
@ -164,18 +102,6 @@ type (
|
||||||
PersonaID string `path:"personaId" validate:"required"`
|
PersonaID string `path:"personaId" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
CopyMissionInspirationReq {
|
|
||||||
Keyword string `json:"keyword,optional"`
|
|
||||||
ContentDirection string `json:"content_direction,optional"`
|
|
||||||
UseWebSearch bool `json:"use_web_search,optional"`
|
|
||||||
AvoidTopics []string `json:"avoid_topics,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CopyMissionInspirationHandlerReq {
|
|
||||||
PersonaCopyMissionsPath
|
|
||||||
CopyMissionInspirationReq
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateCopyMissionHandlerReq {
|
CreateCopyMissionHandlerReq {
|
||||||
PersonaCopyMissionsPath
|
PersonaCopyMissionsPath
|
||||||
CreateCopyMissionReq
|
CreateCopyMissionReq
|
||||||
|
|
@ -256,40 +182,6 @@ type (
|
||||||
Total int `json:"total"`
|
Total int `json:"total"`
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteCopyMissionMatrixDraftsReq {
|
|
||||||
DraftIDs []string `json:"draft_ids,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteCopyMissionMatrixDraftsHandlerReq {
|
|
||||||
CopyMissionPath
|
|
||||||
DeleteCopyMissionMatrixDraftsReq
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteCopyMissionMatrixDraftsData {
|
|
||||||
Deleted int `json:"deleted"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CopyMissionScheduleCopyDraftsReq {
|
|
||||||
AccountID string `json:"account_id" validate:"required"`
|
|
||||||
DraftIDs []string `json:"draft_ids" validate:"required,min=1"`
|
|
||||||
StartAt int64 `json:"start_at,optional"`
|
|
||||||
Timezone string `json:"timezone,optional"`
|
|
||||||
Slots []PublishSlotData `json:"slots,optional"`
|
|
||||||
Mode string `json:"mode,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ScheduleCopyMissionDraftsHandlerReq {
|
|
||||||
CopyMissionPath
|
|
||||||
CopyMissionScheduleCopyDraftsReq
|
|
||||||
}
|
|
||||||
|
|
||||||
CopyMissionScheduleCopyDraftsData {
|
|
||||||
Scheduled int `json:"scheduled"`
|
|
||||||
List []PublishQueueItemData `json:"list"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CopyMissionInspirationSourceData {
|
CopyMissionInspirationSourceData {
|
||||||
Query string `json:"query,omitempty"`
|
Query string `json:"query,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
|
|
@ -298,22 +190,11 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
CopyMissionInspirationData {
|
CopyMissionInspirationData {
|
||||||
TopicCandidateID string `json:"topic_candidate_id,omitempty"`
|
|
||||||
ContentPlanID string `json:"content_plan_id,omitempty"`
|
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
SeedQuery string `json:"seed_query"`
|
SeedQuery string `json:"seed_query"`
|
||||||
Brief string `json:"brief"`
|
Brief string `json:"brief"`
|
||||||
TrendReason string `json:"trend_reason,omitempty"`
|
TrendReason string `json:"trend_reason,omitempty"`
|
||||||
TrendKeywords []string `json:"trend_keywords,omitempty"`
|
TrendKeywords []string `json:"trend_keywords,omitempty"`
|
||||||
Angles []string `json:"angles,omitempty"`
|
|
||||||
Mission string `json:"mission,omitempty"`
|
|
||||||
TargetAudience string `json:"target_audience,omitempty"`
|
|
||||||
OpeningType string `json:"opening_type,omitempty"`
|
|
||||||
BodyType string `json:"body_type,omitempty"`
|
|
||||||
Emotion string `json:"emotion,omitempty"`
|
|
||||||
CtaType string `json:"cta_type,omitempty"`
|
|
||||||
RiskLevel string `json:"risk_level,omitempty"`
|
|
||||||
Avoid []string `json:"avoid,omitempty"`
|
|
||||||
Sources []CopyMissionInspirationSourceData `json:"sources,omitempty"`
|
Sources []CopyMissionInspirationSourceData `json:"sources,omitempty"`
|
||||||
WebSearchUsed bool `json:"web_search_used"`
|
WebSearchUsed bool `json:"web_search_used"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
|
@ -332,7 +213,7 @@ service gateway {
|
||||||
get /:personaId/copy-missions (PersonaCopyMissionsPath) returns (ListCopyMissionsData)
|
get /:personaId/copy-missions (PersonaCopyMissionsPath) returns (ListCopyMissionsData)
|
||||||
|
|
||||||
@handler inspireCopyMission
|
@handler inspireCopyMission
|
||||||
post /:personaId/copy-mission-inspiration (CopyMissionInspirationHandlerReq) returns (CopyMissionInspirationData)
|
post /:personaId/copy-mission-inspiration (PersonaCopyMissionsPath) returns (CopyMissionInspirationData)
|
||||||
|
|
||||||
@handler createCopyMission
|
@handler createCopyMission
|
||||||
post /:personaId/copy-missions (CreateCopyMissionHandlerReq) returns (CopyMissionData)
|
post /:personaId/copy-missions (CreateCopyMissionHandlerReq) returns (CopyMissionData)
|
||||||
|
|
@ -367,30 +248,9 @@ service gateway {
|
||||||
@handler listCopyMissionCopyDrafts
|
@handler listCopyMissionCopyDrafts
|
||||||
get /:personaId/copy-missions/:id/copy-drafts (CopyMissionPath) returns (ListCopyMissionCopyDraftsData)
|
get /:personaId/copy-missions/:id/copy-drafts (CopyMissionPath) returns (ListCopyMissionCopyDraftsData)
|
||||||
|
|
||||||
@handler deleteCopyMissionMatrixDrafts
|
|
||||||
post /:personaId/copy-missions/:id/matrix-drafts/delete (DeleteCopyMissionMatrixDraftsHandlerReq) returns (DeleteCopyMissionMatrixDraftsData)
|
|
||||||
|
|
||||||
@handler scheduleCopyMissionDrafts
|
|
||||||
post /:personaId/copy-missions/:id/copy-drafts/schedule (ScheduleCopyMissionDraftsHandlerReq) returns (CopyMissionScheduleCopyDraftsData)
|
|
||||||
|
|
||||||
@handler getCopyMissionScanSchedule
|
@handler getCopyMissionScanSchedule
|
||||||
get /:personaId/copy-missions/:id/scan-schedule (CopyMissionPath) returns (CopyMissionScanScheduleData)
|
get /:personaId/copy-missions/:id/scan-schedule (CopyMissionPath) returns (CopyMissionScanScheduleData)
|
||||||
|
|
||||||
@handler upsertCopyMissionScanSchedule
|
@handler upsertCopyMissionScanSchedule
|
||||||
put /:personaId/copy-missions/:id/scan-schedule (UpsertCopyMissionScanScheduleHandlerReq) returns (CopyMissionScanScheduleData)
|
put /:personaId/copy-missions/:id/scan-schedule (UpsertCopyMissionScanScheduleHandlerReq) returns (CopyMissionScanScheduleData)
|
||||||
|
|
||||||
@handler patchCopyMissionSimilarAccount
|
|
||||||
patch /:personaId/copy-missions/:id/similar-accounts/:username (PatchSimilarAccountHandlerReq) returns (CopyMissionData)
|
|
||||||
|
|
||||||
@handler patchCopyMissionAudienceSample
|
|
||||||
patch /:personaId/copy-missions/:id/audience-samples/:username (PatchAudienceSampleHandlerReq) returns (CopyMissionData)
|
|
||||||
|
|
||||||
@handler expandCopyMissionGraph
|
|
||||||
post /:personaId/copy-missions/:id/knowledge-graph/expand (ExpandCopyMissionGraphHandlerReq) returns (ExpandKnowledgeGraphData)
|
|
||||||
|
|
||||||
@handler getCopyMissionGraph
|
|
||||||
get /:personaId/copy-missions/:id/knowledge-graph (CopyMissionPath) returns (KnowledgeGraphData)
|
|
||||||
|
|
||||||
@handler patchCopyMissionGraphNodes
|
|
||||||
patch /:personaId/copy-missions/:id/knowledge-graph/nodes (PatchCopyMissionGraphNodesHandlerReq) returns (KnowledgeGraphData)
|
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +39,6 @@ type (
|
||||||
BraveSearchLang string `json:"brave_search_lang"`
|
BraveSearchLang string `json:"brave_search_lang"`
|
||||||
ExaUserLocation string `json:"exa_user_location"`
|
ExaUserLocation string `json:"exa_user_location"`
|
||||||
ExpandStrategy string `json:"expand_strategy"` // brave | llm | hybrid
|
ExpandStrategy string `json:"expand_strategy"` // brave | llm | hybrid
|
||||||
DevModeEnabled bool `json:"dev_mode_enabled"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateMemberPlacementSettingsReq {
|
UpdateMemberPlacementSettingsReq {
|
||||||
|
|
@ -50,17 +49,6 @@ type (
|
||||||
BraveSearchLang *string `json:"brave_search_lang,optional"`
|
BraveSearchLang *string `json:"brave_search_lang,optional"`
|
||||||
ExaUserLocation *string `json:"exa_user_location,optional"`
|
ExaUserLocation *string `json:"exa_user_location,optional"`
|
||||||
ExpandStrategy *string `json:"expand_strategy,optional"`
|
ExpandStrategy *string `json:"expand_strategy,optional"`
|
||||||
DevModeEnabled *bool `json:"dev_mode_enabled,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
MemberCapabilitiesData {
|
|
||||||
DiscoverReady bool `json:"discover_ready"`
|
|
||||||
AiReady bool `json:"ai_ready"`
|
|
||||||
PublishReady bool `json:"publish_ready"`
|
|
||||||
DiscoverHint string `json:"discover_hint,omitempty"`
|
|
||||||
AiHint string `json:"ai_hint,omitempty"`
|
|
||||||
PublishHint string `json:"publish_hint,omitempty"`
|
|
||||||
ActiveThreadsAccountId string `json:"active_threads_account_id,omitempty"`
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -83,7 +71,4 @@ service gateway {
|
||||||
|
|
||||||
@handler updateMemberPlacementSettings
|
@handler updateMemberPlacementSettings
|
||||||
patch /me/placement-settings (UpdateMemberPlacementSettingsReq) returns (MemberPlacementSettingsData)
|
patch /me/placement-settings (UpdateMemberPlacementSettingsReq) returns (MemberPlacementSettingsData)
|
||||||
|
|
||||||
@handler getMemberCapabilities
|
|
||||||
get /me/capabilities returns (MemberCapabilitiesData)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,23 +64,6 @@ type (
|
||||||
StartPersonaStyleAnalysisReq
|
StartPersonaStyleAnalysisReq
|
||||||
}
|
}
|
||||||
|
|
||||||
StartPersonaStyleAnalysisFromTextReq {
|
|
||||||
ReferenceTexts []string `json:"reference_texts,optional"`
|
|
||||||
RawText string `json:"raw_text,optional"`
|
|
||||||
SourceLabel string `json:"source_label,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPersonaStyleAnalysisFromTextData {
|
|
||||||
Persona PersonaData `json:"persona"`
|
|
||||||
PostCount int `json:"post_count"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPersonaStyleAnalysisFromTextHandlerReq {
|
|
||||||
PersonaPath
|
|
||||||
StartPersonaStyleAnalysisFromTextReq
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPersonaViralScanJobReq {
|
StartPersonaViralScanJobReq {
|
||||||
Keywords []string `json:"keywords,optional"`
|
Keywords []string `json:"keywords,optional"`
|
||||||
}
|
}
|
||||||
|
|
@ -130,29 +113,17 @@ type (
|
||||||
CopyDraftData {
|
CopyDraftData {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
PersonaID string `json:"persona_id"`
|
PersonaID string `json:"persona_id"`
|
||||||
ContentPlanID string `json:"content_plan_id,omitempty"`
|
|
||||||
CopyMissionID string `json:"copy_mission_id,omitempty"`
|
CopyMissionID string `json:"copy_mission_id,omitempty"`
|
||||||
ScanPostID string `json:"scan_post_id,omitempty"`
|
ScanPostID string `json:"scan_post_id,omitempty"`
|
||||||
FormulaID string `json:"formula_id,omitempty"`
|
|
||||||
DraftType string `json:"draft_type"`
|
DraftType string `json:"draft_type"`
|
||||||
SortOrder int `json:"sort_order,omitempty"`
|
SortOrder int `json:"sort_order,omitempty"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
TopicTag string `json:"topic_tag,omitempty"`
|
|
||||||
Angle string `json:"angle,omitempty"`
|
Angle string `json:"angle,omitempty"`
|
||||||
Hook string `json:"hook,omitempty"`
|
Hook string `json:"hook,omitempty"`
|
||||||
Rationale string `json:"rationale,omitempty"`
|
Rationale string `json:"rationale,omitempty"`
|
||||||
ReferenceNotes string `json:"reference_notes,omitempty"`
|
ReferenceNotes string `json:"reference_notes,omitempty"`
|
||||||
Sources []string `json:"sources,omitempty"`
|
Sources []string `json:"sources,omitempty"`
|
||||||
AiScore int `json:"ai_score,omitempty"`
|
|
||||||
FormulaScore int `json:"formula_score,omitempty"`
|
|
||||||
BrandFitScore int `json:"brand_fit_score,omitempty"`
|
|
||||||
RiskScore int `json:"risk_score,omitempty"`
|
|
||||||
SimilarityScore int `json:"similarity_score,omitempty"`
|
|
||||||
EngagementPotential int `json:"engagement_potential,omitempty"`
|
|
||||||
FreshnessScore int `json:"freshness_score,omitempty"`
|
|
||||||
ReviewSuggestion string `json:"review_suggestion,omitempty"`
|
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
PublishQueueID string `json:"publish_queue_id,omitempty"`
|
|
||||||
PublishedMediaID string `json:"published_media_id,omitempty"`
|
PublishedMediaID string `json:"published_media_id,omitempty"`
|
||||||
PublishedPermalink string `json:"published_permalink,omitempty"`
|
PublishedPermalink string `json:"published_permalink,omitempty"`
|
||||||
PublishedAt int64 `json:"published_at,omitempty"`
|
PublishedAt int64 `json:"published_at,omitempty"`
|
||||||
|
|
@ -184,202 +155,10 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateCopyDraftReq {
|
UpdateCopyDraftReq {
|
||||||
Text *string `json:"text,optional"`
|
Text *string `json:"text,optional"`
|
||||||
TopicTag *string `json:"topic_tag,optional"`
|
Hook *string `json:"hook,optional"`
|
||||||
Hook *string `json:"hook,optional"`
|
Angle *string `json:"angle,optional"`
|
||||||
Angle *string `json:"angle,optional"`
|
Status *string `json:"status,optional"`
|
||||||
Rationale *string `json:"rationale,optional"`
|
|
||||||
Status *string `json:"status,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
TopicCandidateData {
|
|
||||||
ID string `json:"id"`
|
|
||||||
PersonaID string `json:"persona_id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Source string `json:"source,omitempty"`
|
|
||||||
Category string `json:"category,omitempty"`
|
|
||||||
SeedQuery string `json:"seed_query,omitempty"`
|
|
||||||
TrendReason string `json:"trend_reason,omitempty"`
|
|
||||||
TrendKeywords []string `json:"trend_keywords,omitempty"`
|
|
||||||
HeatScore int `json:"heat_score,omitempty"`
|
|
||||||
FitScore int `json:"fit_score,omitempty"`
|
|
||||||
InteractionScore int `json:"interaction_score,omitempty"`
|
|
||||||
ExtendScore int `json:"extend_score,omitempty"`
|
|
||||||
RiskScore int `json:"risk_score,omitempty"`
|
|
||||||
FinalScore float64 `json:"final_score,omitempty"`
|
|
||||||
RecommendedMissions []string `json:"recommended_missions,omitempty"`
|
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
CreateAt int64 `json:"create_at"`
|
|
||||||
UpdateAt int64 `json:"update_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentPlanData {
|
|
||||||
ID string `json:"id"`
|
|
||||||
PersonaID string `json:"persona_id"`
|
|
||||||
TopicCandidateID string `json:"topic_candidate_id,omitempty"`
|
|
||||||
Topic string `json:"topic"`
|
|
||||||
Mission string `json:"mission"`
|
|
||||||
TargetAudience string `json:"target_audience,omitempty"`
|
|
||||||
Angle string `json:"angle,omitempty"`
|
|
||||||
OpeningType string `json:"opening_type,omitempty"`
|
|
||||||
BodyType string `json:"body_type,omitempty"`
|
|
||||||
Emotion string `json:"emotion,omitempty"`
|
|
||||||
EndingType string `json:"ending_type,omitempty"`
|
|
||||||
CtaType string `json:"cta_type,omitempty"`
|
|
||||||
RiskLevel string `json:"risk_level,omitempty"`
|
|
||||||
RequiresHumanReview bool `json:"requires_human_review,omitempty"`
|
|
||||||
Avoid []string `json:"avoid,omitempty"`
|
|
||||||
SelectedKnowledge []string `json:"selected_knowledge,omitempty"`
|
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
CreateAt int64 `json:"create_at"`
|
|
||||||
UpdateAt int64 `json:"update_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
FeedbackEventData {
|
|
||||||
ID string `json:"id"`
|
|
||||||
PersonaID string `json:"persona_id"`
|
|
||||||
ContentPlanID string `json:"content_plan_id,omitempty"`
|
|
||||||
DraftID string `json:"draft_id,omitempty"`
|
|
||||||
Decision string `json:"decision"`
|
|
||||||
Note string `json:"note,omitempty"`
|
|
||||||
Snapshot string `json:"snapshot,omitempty"`
|
|
||||||
CreateAt int64 `json:"create_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
KnowledgeSourceData {
|
|
||||||
ID string `json:"id"`
|
|
||||||
PersonaID string `json:"persona_id"`
|
|
||||||
SourceType string `json:"source_type"`
|
|
||||||
Title string `json:"title,omitempty"`
|
|
||||||
Filename string `json:"filename,omitempty"`
|
|
||||||
ParsedStatus string `json:"parsed_status,omitempty"`
|
|
||||||
ChunkCount int `json:"chunk_count,omitempty"`
|
|
||||||
CreateAt int64 `json:"create_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
KnowledgeChunkData {
|
|
||||||
ID string `json:"id"`
|
|
||||||
PersonaID string `json:"persona_id"`
|
|
||||||
SourceID string `json:"source_id"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Topics []string `json:"topics,omitempty"`
|
|
||||||
StyleTags []string `json:"style_tags,omitempty"`
|
|
||||||
RiskLevel string `json:"risk_level,omitempty"`
|
|
||||||
CreateAt int64 `json:"create_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
FormulaPoolData {
|
|
||||||
ID string `json:"id"`
|
|
||||||
PersonaID string `json:"persona_id"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Pattern string `json:"pattern,omitempty"`
|
|
||||||
UseCases []string `json:"use_cases,omitempty"`
|
|
||||||
Avoid []string `json:"avoid,omitempty"`
|
|
||||||
Weight int `json:"weight,omitempty"`
|
|
||||||
CreateAt int64 `json:"create_at"`
|
|
||||||
UpdateAt int64 `json:"update_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateKnowledgeSourceReq {
|
|
||||||
SourceType string `json:"source_type" validate:"required"`
|
|
||||||
Title string `json:"title,optional"`
|
|
||||||
Filename string `json:"filename,optional"`
|
|
||||||
Content string `json:"content,optional"`
|
|
||||||
ContentBase64 string `json:"content_base64,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateKnowledgeSourceHandlerReq {
|
|
||||||
PersonaPath
|
|
||||||
CreateKnowledgeSourceReq
|
|
||||||
}
|
|
||||||
|
|
||||||
ListKnowledgeSourcesData { List []KnowledgeSourceData `json:"list"` }
|
|
||||||
ListKnowledgeChunksData { List []KnowledgeChunkData `json:"list"` }
|
|
||||||
ListFormulaPoolsData { List []FormulaPoolData `json:"list"` }
|
|
||||||
|
|
||||||
CreateFormulaPoolReq {
|
|
||||||
Type string `json:"type" validate:"required"`
|
|
||||||
Name string `json:"name" validate:"required"`
|
|
||||||
Pattern string `json:"pattern,optional"`
|
|
||||||
UseCases []string `json:"use_cases,optional"`
|
|
||||||
Avoid []string `json:"avoid,optional"`
|
|
||||||
Weight int `json:"weight,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateFormulaPoolHandlerReq {
|
|
||||||
PersonaPath
|
|
||||||
CreateFormulaPoolReq
|
|
||||||
}
|
|
||||||
|
|
||||||
ListTopicCandidatesData {
|
|
||||||
List []TopicCandidateData `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListContentPlansData {
|
|
||||||
List []ContentPlanData `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateContentPlanReq {
|
|
||||||
TopicCandidateID string `json:"topic_candidate_id,optional"`
|
|
||||||
Topic string `json:"topic" validate:"required"`
|
|
||||||
Mission string `json:"mission" validate:"required"`
|
|
||||||
TargetAudience string `json:"target_audience,optional"`
|
|
||||||
Angle string `json:"angle,optional"`
|
|
||||||
OpeningType string `json:"opening_type,optional"`
|
|
||||||
BodyType string `json:"body_type,optional"`
|
|
||||||
Emotion string `json:"emotion,optional"`
|
|
||||||
EndingType string `json:"ending_type,optional"`
|
|
||||||
CtaType string `json:"cta_type,optional"`
|
|
||||||
RiskLevel string `json:"risk_level,optional"`
|
|
||||||
RequiresHumanReview bool `json:"requires_human_review,optional"`
|
|
||||||
Avoid []string `json:"avoid,optional"`
|
|
||||||
SelectedKnowledge []string `json:"selected_knowledge,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateContentPlanHandlerReq {
|
|
||||||
PersonaPath
|
|
||||||
CreateContentPlanReq
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentPlanPath {
|
|
||||||
ID string `path:"id" validate:"required"`
|
|
||||||
ContentPlanID string `path:"contentPlanId" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateContentPlanReq {
|
|
||||||
Topic *string `json:"topic,optional"`
|
|
||||||
Mission *string `json:"mission,optional"`
|
|
||||||
TargetAudience *string `json:"target_audience,optional"`
|
|
||||||
Angle *string `json:"angle,optional"`
|
|
||||||
OpeningType *string `json:"opening_type,optional"`
|
|
||||||
BodyType *string `json:"body_type,optional"`
|
|
||||||
Emotion *string `json:"emotion,optional"`
|
|
||||||
EndingType *string `json:"ending_type,optional"`
|
|
||||||
CtaType *string `json:"cta_type,optional"`
|
|
||||||
RiskLevel *string `json:"risk_level,optional"`
|
|
||||||
RequiresHumanReview *bool `json:"requires_human_review,optional"`
|
|
||||||
Avoid []string `json:"avoid,optional"`
|
|
||||||
SelectedKnowledge []string `json:"selected_knowledge,optional"`
|
|
||||||
Status *string `json:"status,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateContentPlanHandlerReq {
|
|
||||||
ContentPlanPath
|
|
||||||
UpdateContentPlanReq
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateFeedbackEventReq {
|
|
||||||
ContentPlanID string `json:"content_plan_id,optional"`
|
|
||||||
DraftID string `json:"draft_id,optional"`
|
|
||||||
Decision string `json:"decision" validate:"required"`
|
|
||||||
Note string `json:"note,optional"`
|
|
||||||
Snapshot string `json:"snapshot,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateFeedbackEventHandlerReq {
|
|
||||||
PersonaPath
|
|
||||||
CreateFeedbackEventReq
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateCopyDraftHandlerReq {
|
UpdateCopyDraftHandlerReq {
|
||||||
|
|
@ -388,9 +167,8 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
PublishCopyDraftReq {
|
PublishCopyDraftReq {
|
||||||
Text string `json:"text,optional"`
|
Text string `json:"text,optional"`
|
||||||
TopicTag string `json:"topic_tag,optional"`
|
Confirm bool `json:"confirm"`
|
||||||
Confirm bool `json:"confirm"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PublishCopyDraftHandlerReq {
|
PublishCopyDraftHandlerReq {
|
||||||
|
|
@ -405,217 +183,6 @@ type (
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
ScheduleCopyDraftsReq {
|
|
||||||
AccountID string `json:"account_id" validate:"required"`
|
|
||||||
DraftIDs []string `json:"draft_ids" validate:"required,min=1"`
|
|
||||||
StartAt int64 `json:"start_at,optional"`
|
|
||||||
Timezone string `json:"timezone,optional"`
|
|
||||||
Slots []PublishSlotData `json:"slots,optional"`
|
|
||||||
Mode string `json:"mode,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ScheduleCopyDraftsData {
|
|
||||||
Scheduled int `json:"scheduled"`
|
|
||||||
List []PublishQueueItemData `json:"list"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
SchedulePersonaDraftsHandlerReq {
|
|
||||||
PersonaPath
|
|
||||||
ScheduleCopyDraftsReq
|
|
||||||
}
|
|
||||||
|
|
||||||
StylePresetData {
|
|
||||||
ID string `json:"id"`
|
|
||||||
PersonaID string `json:"persona_id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Tone string `json:"tone,omitempty"`
|
|
||||||
CTA []string `json:"cta,omitempty"`
|
|
||||||
BannedWords []string `json:"banned_words,omitempty"`
|
|
||||||
Notes string `json:"notes,omitempty"`
|
|
||||||
CreateAt int64 `json:"create_at"`
|
|
||||||
UpdateAt int64 `json:"update_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListStylePresetsData {
|
|
||||||
List []StylePresetData `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
UpsertStylePresetReq {
|
|
||||||
Name string `json:"name" validate:"required"`
|
|
||||||
Tone string `json:"tone,optional"`
|
|
||||||
CTA []string `json:"cta,optional"`
|
|
||||||
BannedWords []string `json:"banned_words,optional"`
|
|
||||||
Notes string `json:"notes,optional"`
|
|
||||||
Apply bool `json:"apply,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
StylePresetPath {
|
|
||||||
ID string `path:"id" validate:"required"`
|
|
||||||
PresetID string `path:"presetId" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
UpsertStylePresetHandlerReq {
|
|
||||||
StylePresetPath
|
|
||||||
UpsertStylePresetReq
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteCopyDraftData {
|
|
||||||
DraftID string `json:"draft_id"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PrunePersonaCopyDraftsReq {
|
|
||||||
Keep int `json:"keep,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PrunePersonaCopyDraftsHandlerReq {
|
|
||||||
PersonaPath
|
|
||||||
PrunePersonaCopyDraftsReq
|
|
||||||
}
|
|
||||||
|
|
||||||
PrunePersonaCopyDraftsData {
|
|
||||||
Deleted int64 `json:"deleted"`
|
|
||||||
Kept int `json:"kept"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListPersonaContentInboxReq {
|
|
||||||
Page int64 `form:"page,optional"`
|
|
||||||
PageSize int64 `form:"pageSize,optional"`
|
|
||||||
Status string `form:"status,optional"`
|
|
||||||
RangeStart int64 `form:"rangeStart,optional"`
|
|
||||||
RangeEnd int64 `form:"rangeEnd,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListPersonaContentInboxHandlerReq {
|
|
||||||
PersonaPath
|
|
||||||
ListPersonaContentInboxReq
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentInboxItemData {
|
|
||||||
CopyDraftData
|
|
||||||
ScheduledAt int64 `json:"scheduled_at,omitempty"`
|
|
||||||
Lifecycle string `json:"lifecycle"`
|
|
||||||
GroupDate int64 `json:"group_date"`
|
|
||||||
FormulaLabel string `json:"formula_label,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListPersonaContentInboxData {
|
|
||||||
Pagination PaginationData `json:"pagination"`
|
|
||||||
List []ContentInboxItemData `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
SchedulePersonaCopyDraftReq {
|
|
||||||
AccountID string `json:"account_id" validate:"required"`
|
|
||||||
TopicTag string `json:"topic_tag,optional"`
|
|
||||||
ScheduledAt int64 `json:"scheduled_at,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
SchedulePersonaCopyDraftHandlerReq {
|
|
||||||
CopyDraftPath
|
|
||||||
SchedulePersonaCopyDraftReq
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentFormulaPath {
|
|
||||||
ID string `path:"id" validate:"required"`
|
|
||||||
FormulaID string `path:"formulaId" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
GenerateFromContentFormulaReq {
|
|
||||||
AccountID string `json:"account_id" validate:"required"`
|
|
||||||
Topic string `json:"topic" validate:"required"`
|
|
||||||
Brief string `json:"brief,optional"`
|
|
||||||
UseWebSearch bool `json:"use_web_search,optional"`
|
|
||||||
DraftCount int `json:"draft_count,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
GenerateFromContentFormulaHandlerReq {
|
|
||||||
ContentFormulaPath
|
|
||||||
GenerateFromContentFormulaReq
|
|
||||||
}
|
|
||||||
|
|
||||||
GenerateFromContentFormulaData {
|
|
||||||
List []CopyDraftData `json:"list"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
GeneratePersonaTopicMatrixReq {
|
|
||||||
Topic string `json:"topic" validate:"required"`
|
|
||||||
ContentPlanID string `json:"content_plan_id,optional"`
|
|
||||||
Brief string `json:"brief,optional"`
|
|
||||||
UseWebSearch bool `json:"use_web_search,optional"`
|
|
||||||
DraftCount int `json:"draft_count,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
GeneratePersonaTopicMatrixHandlerReq {
|
|
||||||
PersonaPath
|
|
||||||
GeneratePersonaTopicMatrixReq
|
|
||||||
}
|
|
||||||
|
|
||||||
GeneratePersonaTopicMatrixData {
|
|
||||||
List []CopyDraftData `json:"list"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPersonaTopicMatrixJobHandlerReq {
|
|
||||||
PersonaPath
|
|
||||||
GeneratePersonaTopicMatrixReq
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPersonaTopicMatrixJobData {
|
|
||||||
JobID string `json:"job_id"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
AiProvider string `json:"ai_provider"`
|
|
||||||
AiModel string `json:"ai_model"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPersonaFormulaDraftJobReq {
|
|
||||||
AccountID string `json:"account_id" validate:"required"`
|
|
||||||
FormulaID string `json:"formula_id" validate:"required"`
|
|
||||||
Topic string `json:"topic" validate:"required"`
|
|
||||||
Brief string `json:"brief,optional"`
|
|
||||||
UseWebSearch bool `json:"use_web_search,optional"`
|
|
||||||
DraftCount int `json:"draft_count,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPersonaFormulaDraftJobHandlerReq {
|
|
||||||
PersonaPath
|
|
||||||
StartPersonaFormulaDraftJobReq
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPersonaFormulaDraftJobData {
|
|
||||||
JobID string `json:"job_id"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
AiProvider string `json:"ai_provider"`
|
|
||||||
AiModel string `json:"ai_model"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPersonaRewriteDraftJobReq {
|
|
||||||
AccountID string `json:"account_id" validate:"required"`
|
|
||||||
ReferenceText string `json:"reference_text" validate:"required"`
|
|
||||||
Topic string `json:"topic" validate:"required"`
|
|
||||||
Brief string `json:"brief,optional"`
|
|
||||||
SaveLabel string `json:"save_label,optional"`
|
|
||||||
UseWebSearch bool `json:"use_web_search,optional"`
|
|
||||||
DraftCount int `json:"draft_count,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPersonaRewriteDraftJobHandlerReq {
|
|
||||||
PersonaPath
|
|
||||||
StartPersonaRewriteDraftJobReq
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPersonaRewriteDraftJobData {
|
|
||||||
JobID string `json:"job_id"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
AiProvider string `json:"ai_provider"`
|
|
||||||
AiModel string `json:"ai_model"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@server(
|
@server(
|
||||||
|
|
@ -644,9 +211,6 @@ service gateway {
|
||||||
@handler startPersonaStyleAnalysis
|
@handler startPersonaStyleAnalysis
|
||||||
post /:id/style-analysis (StartPersonaStyleAnalysisHandlerReq) returns (StartPersonaStyleAnalysisData)
|
post /:id/style-analysis (StartPersonaStyleAnalysisHandlerReq) returns (StartPersonaStyleAnalysisData)
|
||||||
|
|
||||||
@handler startPersonaStyleAnalysisFromText
|
|
||||||
post /:id/style-analysis-from-text (StartPersonaStyleAnalysisFromTextHandlerReq) returns (StartPersonaStyleAnalysisFromTextData)
|
|
||||||
|
|
||||||
@handler startPersonaViralScanJob
|
@handler startPersonaViralScanJob
|
||||||
post /:id/viral-scan-jobs (StartPersonaViralScanJobHandlerReq) returns (StartPersonaViralScanJobData)
|
post /:id/viral-scan-jobs (StartPersonaViralScanJobHandlerReq) returns (StartPersonaViralScanJobData)
|
||||||
|
|
||||||
|
|
@ -656,39 +220,6 @@ service gateway {
|
||||||
@handler listPersonaCopyDrafts
|
@handler listPersonaCopyDrafts
|
||||||
get /:id/copy-drafts (PersonaPath) returns (ListPersonaCopyDraftsData)
|
get /:id/copy-drafts (PersonaPath) returns (ListPersonaCopyDraftsData)
|
||||||
|
|
||||||
@handler listPersonaContentInbox
|
|
||||||
get /:id/content-inbox (ListPersonaContentInboxHandlerReq) returns (ListPersonaContentInboxData)
|
|
||||||
|
|
||||||
@handler listTopicCandidates
|
|
||||||
get /:id/topic-candidates (PersonaPath) returns (ListTopicCandidatesData)
|
|
||||||
|
|
||||||
@handler listContentPlans
|
|
||||||
get /:id/content-plans (PersonaPath) returns (ListContentPlansData)
|
|
||||||
|
|
||||||
@handler createContentPlan
|
|
||||||
post /:id/content-plans (CreateContentPlanHandlerReq) returns (ContentPlanData)
|
|
||||||
|
|
||||||
@handler updateContentPlan
|
|
||||||
patch /:id/content-plans/:contentPlanId (UpdateContentPlanHandlerReq) returns (ContentPlanData)
|
|
||||||
|
|
||||||
@handler createFeedbackEvent
|
|
||||||
post /:id/feedback-events (CreateFeedbackEventHandlerReq) returns (FeedbackEventData)
|
|
||||||
|
|
||||||
@handler createKnowledgeSource
|
|
||||||
post /:id/knowledge-sources (CreateKnowledgeSourceHandlerReq) returns (KnowledgeSourceData)
|
|
||||||
|
|
||||||
@handler listKnowledgeSources
|
|
||||||
get /:id/knowledge-sources (PersonaPath) returns (ListKnowledgeSourcesData)
|
|
||||||
|
|
||||||
@handler listKnowledgeChunks
|
|
||||||
get /:id/knowledge-chunks (PersonaPath) returns (ListKnowledgeChunksData)
|
|
||||||
|
|
||||||
@handler listFormulaPools
|
|
||||||
get /:id/formula-pools (PersonaPath) returns (ListFormulaPoolsData)
|
|
||||||
|
|
||||||
@handler createFormulaPool
|
|
||||||
post /:id/formula-pools (CreateFormulaPoolHandlerReq) returns (FormulaPoolData)
|
|
||||||
|
|
||||||
@handler generatePersonaCopyDraft
|
@handler generatePersonaCopyDraft
|
||||||
post /:id/copy-drafts/generate (GeneratePersonaCopyDraftHandlerReq) returns (GeneratePersonaCopyDraftData)
|
post /:id/copy-drafts/generate (GeneratePersonaCopyDraftHandlerReq) returns (GeneratePersonaCopyDraftData)
|
||||||
|
|
||||||
|
|
@ -697,40 +228,4 @@ service gateway {
|
||||||
|
|
||||||
@handler publishPersonaCopyDraft
|
@handler publishPersonaCopyDraft
|
||||||
post /:id/copy-drafts/:draftId/publish (PublishCopyDraftHandlerReq) returns (PublishCopyDraftData)
|
post /:id/copy-drafts/:draftId/publish (PublishCopyDraftHandlerReq) returns (PublishCopyDraftData)
|
||||||
|
|
||||||
@handler schedulePersonaDrafts
|
|
||||||
post /:id/copy-drafts/schedule (SchedulePersonaDraftsHandlerReq) returns (ScheduleCopyDraftsData)
|
|
||||||
|
|
||||||
@handler schedulePersonaCopyDraft
|
|
||||||
post /:id/copy-drafts/:draftId/schedule (SchedulePersonaCopyDraftHandlerReq) returns (ScheduleCopyDraftsData)
|
|
||||||
|
|
||||||
@handler generateFromContentFormula
|
|
||||||
post /:id/content-formulas/:formulaId/generate (GenerateFromContentFormulaHandlerReq) returns (GenerateFromContentFormulaData)
|
|
||||||
|
|
||||||
@handler generatePersonaTopicMatrix
|
|
||||||
post /:id/topic-matrix/generate (GeneratePersonaTopicMatrixHandlerReq) returns (GeneratePersonaTopicMatrixData)
|
|
||||||
|
|
||||||
@handler startPersonaTopicMatrixJob
|
|
||||||
post /:id/topic-matrix/jobs (StartPersonaTopicMatrixJobHandlerReq) returns (StartPersonaTopicMatrixJobData)
|
|
||||||
|
|
||||||
@handler startPersonaFormulaDraftJob
|
|
||||||
post /:id/formula-draft/jobs (StartPersonaFormulaDraftJobHandlerReq) returns (StartPersonaFormulaDraftJobData)
|
|
||||||
|
|
||||||
@handler startPersonaRewriteDraftJob
|
|
||||||
post /:id/rewrite-draft/jobs (StartPersonaRewriteDraftJobHandlerReq) returns (StartPersonaRewriteDraftJobData)
|
|
||||||
|
|
||||||
@handler deletePersonaCopyDraft
|
|
||||||
delete /:id/copy-drafts/:draftId (CopyDraftPath) returns (DeleteCopyDraftData)
|
|
||||||
|
|
||||||
@handler prunePersonaCopyDrafts
|
|
||||||
post /:id/copy-drafts/prune (PrunePersonaCopyDraftsHandlerReq) returns (PrunePersonaCopyDraftsData)
|
|
||||||
|
|
||||||
@handler listStylePresets
|
|
||||||
get /:id/style-presets (PersonaPath) returns (ListStylePresetsData)
|
|
||||||
|
|
||||||
@handler upsertStylePreset
|
|
||||||
put /:id/style-presets/:presetId (UpsertStylePresetHandlerReq) returns (StylePresetData)
|
|
||||||
|
|
||||||
@handler deleteStylePreset
|
|
||||||
delete /:id/style-presets/:presetId (StylePresetPath)
|
|
||||||
}
|
}
|
||||||
|
|
@ -83,12 +83,6 @@ type (
|
||||||
PublishOutreachDraftReq
|
PublishOutreachDraftReq
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdatePlacementTopicOutreachDraftHandlerReq {
|
|
||||||
PlacementTopicPath
|
|
||||||
DraftID string `path:"draftId" validate:"required"`
|
|
||||||
UpdateOutreachDraftReq
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchPlacementTopicScanPostOutreachHandlerReq {
|
PatchPlacementTopicScanPostOutreachHandlerReq {
|
||||||
PlacementTopicPath
|
PlacementTopicPath
|
||||||
PostID string `path:"postId"`
|
PostID string `path:"postId"`
|
||||||
|
|
@ -122,25 +116,6 @@ type (
|
||||||
PlacementTopicPath
|
PlacementTopicPath
|
||||||
UpsertBrandScanScheduleReq
|
UpsertBrandScanScheduleReq
|
||||||
}
|
}
|
||||||
|
|
||||||
StartPlacementTopicOutreachDraftJobReq {
|
|
||||||
ScanPostID string `json:"scan_post_id,optional"`
|
|
||||||
ScanPostIDs []string `json:"scan_post_ids,optional"`
|
|
||||||
Count int `json:"count,optional"`
|
|
||||||
VoicePersonaID string `json:"voice_persona_id,optional"`
|
|
||||||
ProductID string `json:"product_id,optional"`
|
|
||||||
Regenerate bool `json:"regenerate,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPlacementTopicOutreachDraftJobHandlerReq {
|
|
||||||
PlacementTopicPath
|
|
||||||
StartPlacementTopicOutreachDraftJobReq
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPlacementTopicOutreachDraftJobsData {
|
|
||||||
Jobs []StartBrandScanJobData `json:"jobs"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@server(
|
@server(
|
||||||
|
|
@ -184,15 +159,9 @@ service gateway {
|
||||||
@handler generatePlacementTopicOutreachDrafts
|
@handler generatePlacementTopicOutreachDrafts
|
||||||
post /:id/outreach-drafts/generate (GeneratePlacementTopicOutreachDraftsHandlerReq) returns (GenerateOutreachDraftsData)
|
post /:id/outreach-drafts/generate (GeneratePlacementTopicOutreachDraftsHandlerReq) returns (GenerateOutreachDraftsData)
|
||||||
|
|
||||||
@handler startPlacementTopicOutreachDraftJob
|
|
||||||
post /:id/outreach-draft-jobs (StartPlacementTopicOutreachDraftJobHandlerReq) returns (StartPlacementTopicOutreachDraftJobsData)
|
|
||||||
|
|
||||||
@handler publishPlacementTopicOutreachDraft
|
@handler publishPlacementTopicOutreachDraft
|
||||||
post /:id/outreach-drafts/publish (PublishPlacementTopicOutreachDraftHandlerReq) returns (PublishOutreachDraftData)
|
post /:id/outreach-drafts/publish (PublishPlacementTopicOutreachDraftHandlerReq) returns (PublishOutreachDraftData)
|
||||||
|
|
||||||
@handler updatePlacementTopicOutreachDraft
|
|
||||||
patch /:id/outreach-drafts/:draftId (UpdatePlacementTopicOutreachDraftHandlerReq) returns (GenerateOutreachDraftsData)
|
|
||||||
|
|
||||||
@handler patchPlacementTopicScanPostOutreach
|
@handler patchPlacementTopicScanPostOutreach
|
||||||
patch /:id/scan-posts/:postId (PatchPlacementTopicScanPostOutreachHandlerReq) returns (ScanPostData)
|
patch /:id/scan-posts/:postId (PatchPlacementTopicScanPostOutreachHandlerReq) returns (ScanPostData)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,8 @@ type (
|
||||||
ThreadsUserID string `json:"threads_user_id,omitempty"`
|
ThreadsUserID string `json:"threads_user_id,omitempty"`
|
||||||
PersonaID string `json:"persona_id,omitempty"` // deprecated: persona is chosen per publish, not bound to account
|
PersonaID string `json:"persona_id,omitempty"` // deprecated: persona is chosen per publish, not bound to account
|
||||||
BrowserConnected bool `json:"browser_connected"`
|
BrowserConnected bool `json:"browser_connected"`
|
||||||
ApiConnected bool `json:"api_connected"`
|
ApiConnected bool `json:"api_connected"`
|
||||||
APITokenExpiresAt int64 `json:"api_token_expires_at,omitempty"`
|
Status string `json:"status"`
|
||||||
Status string `json:"status"`
|
|
||||||
CreateAt int64 `json:"create_at"`
|
CreateAt int64 `json:"create_at"`
|
||||||
UpdateAt int64 `json:"update_at"`
|
UpdateAt int64 `json:"update_at"`
|
||||||
}
|
}
|
||||||
|
|
@ -20,12 +19,6 @@ type (
|
||||||
ActiveAccountID string `json:"active_account_id"`
|
ActiveAccountID string `json:"active_account_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteThreadsAccountData {
|
|
||||||
DeletedID string `json:"deleted_id"`
|
|
||||||
ActiveAccountID string `json:"active_account_id,omitempty"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateThreadsAccountReq {
|
CreateThreadsAccountReq {
|
||||||
DisplayName string `json:"display_name,optional"`
|
DisplayName string `json:"display_name,optional"`
|
||||||
Activate *bool `json:"activate,optional"`
|
Activate *bool `json:"activate,optional"`
|
||||||
|
|
@ -144,711 +137,6 @@ type (
|
||||||
Streams bool `json:"streams"`
|
Streams bool `json:"streams"`
|
||||||
Error string `json:"error,optional"`
|
Error string `json:"error,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
StartThreadsOAuthQuery {
|
|
||||||
AccountID string `form:"account_id,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
StartThreadsOAuthData {
|
|
||||||
AuthorizeURL string `json:"authorize_url"`
|
|
||||||
State string `json:"state"`
|
|
||||||
AccountID string `json:"account_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsOAuthCallbackQuery {
|
|
||||||
Code string `form:"code,optional"`
|
|
||||||
State string `form:"state,optional"`
|
|
||||||
Error string `form:"error,optional"`
|
|
||||||
ErrorReason string `form:"error_reason,optional"`
|
|
||||||
ErrorDescription string `form:"error_description,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsOAuthLogItem {
|
|
||||||
At int64 `json:"at"`
|
|
||||||
Level string `json:"level"`
|
|
||||||
Stage string `json:"stage"`
|
|
||||||
AccountID string `json:"account_id,omitempty"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListThreadsOAuthLogsData {
|
|
||||||
List []ThreadsOAuthLogItem `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsOAuthConfigData {
|
|
||||||
RedirectURI string `json:"redirect_uri"`
|
|
||||||
SuccessRedirect string `json:"success_redirect"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
RequiresHTTPS bool `json:"requires_https"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsAPISmokeTestItem {
|
|
||||||
Scope string `json:"scope"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Count int `json:"count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
RunThreadsAPISmokeTestData {
|
|
||||||
List []ThreadsAPISmokeTestItem `json:"list"`
|
|
||||||
Passed int `json:"passed"`
|
|
||||||
Failed int `json:"failed"`
|
|
||||||
Skipped int `json:"skipped"`
|
|
||||||
Total int `json:"total"`
|
|
||||||
}
|
|
||||||
|
|
||||||
RunThreadsAPIPlaygroundReq {
|
|
||||||
Action string `json:"action" validate:"required"`
|
|
||||||
Query string `json:"query,optional"`
|
|
||||||
Username string `json:"username,optional"`
|
|
||||||
MediaID string `json:"media_id,optional"`
|
|
||||||
ReplyID string `json:"reply_id,optional"`
|
|
||||||
ReplyToID string `json:"reply_to_id,optional"`
|
|
||||||
Text string `json:"text,optional"`
|
|
||||||
Permalink string `json:"permalink,optional"`
|
|
||||||
HintText string `json:"hint_text,optional"`
|
|
||||||
SkipPrime bool `json:"skip_prime,optional"`
|
|
||||||
LocationQuery string `json:"location_query,optional"`
|
|
||||||
Limit int `json:"limit,optional"`
|
|
||||||
Hide bool `json:"hide,optional"`
|
|
||||||
SearchType string `json:"search_type,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
RunThreadsAPIPlaygroundHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
RunThreadsAPIPlaygroundReq
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsAPIPlaygroundData {
|
|
||||||
Action string `json:"action"`
|
|
||||||
Ok bool `json:"ok"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Data string `json:"data,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListThreadsPostPerformanceQuery {
|
|
||||||
Limit int `form:"limit,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListThreadsPostPerformanceHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
ListThreadsPostPerformanceQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsPostMetricCapability {
|
|
||||||
Scope string `json:"scope"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Metrics []string `json:"metrics"`
|
|
||||||
Trackable bool `json:"trackable"`
|
|
||||||
Note string `json:"note,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsInsightMetricItem {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Value int `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsAccountInsightsBlock {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Message string `json:"message,optional"`
|
|
||||||
Metrics []ThreadsInsightMetricItem `json:"metrics,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsPostPerformanceItem {
|
|
||||||
MediaID string `json:"media_id"`
|
|
||||||
Text string `json:"text,optional"`
|
|
||||||
Permalink string `json:"permalink,optional"`
|
|
||||||
Timestamp string `json:"timestamp,optional"`
|
|
||||||
MediaType string `json:"media_type,optional"`
|
|
||||||
MediaURL string `json:"media_url,optional"`
|
|
||||||
ThumbnailURL string `json:"thumbnail_url,optional"`
|
|
||||||
TopicTag string `json:"topic_tag,optional"`
|
|
||||||
Shortcode string `json:"shortcode,optional"`
|
|
||||||
LikeCount int `json:"like_count"`
|
|
||||||
ReplyCount int `json:"reply_count"`
|
|
||||||
RepostCount int `json:"repost_count,optional"`
|
|
||||||
QuoteCount int `json:"quote_count,optional"`
|
|
||||||
Views int `json:"views,optional"`
|
|
||||||
Shares int `json:"shares,optional"`
|
|
||||||
InsightsStatus string `json:"insights_status,optional"`
|
|
||||||
InsightsMessage string `json:"insights_message,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsPostPerformanceData {
|
|
||||||
AccountID string `json:"account_id"`
|
|
||||||
Username string `json:"username,optional"`
|
|
||||||
ThreadsUserID string `json:"threads_user_id,optional"`
|
|
||||||
FetchedAt int64 `json:"fetched_at"`
|
|
||||||
Capabilities []ThreadsPostMetricCapability `json:"capabilities"`
|
|
||||||
AccountInsights ThreadsAccountInsightsBlock `json:"account_insights"`
|
|
||||||
Posts []ThreadsPostPerformanceItem `json:"posts"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishQueueItemPath {
|
|
||||||
ID string `path:"id" validate:"required"`
|
|
||||||
QID string `path:"qid" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListPublishQueueQuery {
|
|
||||||
Status string `form:"status,optional"`
|
|
||||||
Page int `form:"page,optional"`
|
|
||||||
PageSize int `form:"pageSize,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListPublishQueueHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
ListPublishQueueQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
CreatePublishQueueReq {
|
|
||||||
Text string `json:"text" validate:"required"`
|
|
||||||
TopicTag string `json:"topic_tag,optional"`
|
|
||||||
ScheduledAt int64 `json:"scheduled_at,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CreatePublishQueueHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
CreatePublishQueueReq
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchPublishQueueReq {
|
|
||||||
Text *string `json:"text,optional"`
|
|
||||||
TopicTag *string `json:"topic_tag,optional"`
|
|
||||||
ScheduledAt *int64 `json:"scheduled_at,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchPublishQueueHandlerReq {
|
|
||||||
PublishQueueItemPath
|
|
||||||
PatchPublishQueueReq
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishQueueItemData {
|
|
||||||
ID string `json:"id"`
|
|
||||||
AccountID string `json:"account_id"`
|
|
||||||
PersonaID string `json:"persona_id,omitempty"`
|
|
||||||
CopyMissionID string `json:"copy_mission_id,omitempty"`
|
|
||||||
CopyDraftID string `json:"copy_draft_id,omitempty"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
TopicTag string `json:"topic_tag,omitempty"`
|
|
||||||
ScheduledAt int64 `json:"scheduled_at"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
MediaID string `json:"media_id,optional"`
|
|
||||||
Permalink string `json:"permalink,optional"`
|
|
||||||
PublishedAt int64 `json:"published_at,optional"`
|
|
||||||
ErrorMessage string `json:"error_message,optional"`
|
|
||||||
RetryCount int `json:"retry_count,omitempty"`
|
|
||||||
LastAttemptAt int64 `json:"last_attempt_at,omitempty"`
|
|
||||||
NextAttemptAt int64 `json:"next_attempt_at,omitempty"`
|
|
||||||
MissedAt int64 `json:"missed_at,omitempty"`
|
|
||||||
PausedReason string `json:"paused_reason,omitempty"`
|
|
||||||
CreateAt int64 `json:"create_at"`
|
|
||||||
UpdateAt int64 `json:"update_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishQueueListData {
|
|
||||||
Pagination PaginationData `json:"pagination"`
|
|
||||||
List []PublishQueueItemData `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListThreadsPublishHealthQuery {
|
|
||||||
Page int `form:"page,optional"`
|
|
||||||
PageSize int `form:"pageSize,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListThreadsPublishHealthHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
ListThreadsPublishHealthQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsPublishHealthSummary {
|
|
||||||
PendingScheduled int64 `json:"pending_scheduled"`
|
|
||||||
FailedCount int64 `json:"failed_count"`
|
|
||||||
Published7d int64 `json:"published_7d"`
|
|
||||||
TotalLikes7d int `json:"total_likes_7d"`
|
|
||||||
TotalReplies7d int `json:"total_replies_7d"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsPublishHealthCheckpoint {
|
|
||||||
Checkpoint string `json:"checkpoint"`
|
|
||||||
LikeCount int `json:"like_count"`
|
|
||||||
ReplyCount int `json:"reply_count"`
|
|
||||||
RepostCount int `json:"repost_count"`
|
|
||||||
QuoteCount int `json:"quote_count"`
|
|
||||||
Views int `json:"views,optional"`
|
|
||||||
CheckedAt int64 `json:"checked_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsPublishHealthItem {
|
|
||||||
MediaID string `json:"media_id"`
|
|
||||||
Permalink string `json:"permalink,optional"`
|
|
||||||
Text string `json:"text,optional"`
|
|
||||||
PublishedAt int64 `json:"published_at"`
|
|
||||||
LikeCount int `json:"like_count"`
|
|
||||||
ReplyCount int `json:"reply_count"`
|
|
||||||
RepostCount int `json:"repost_count,optional"`
|
|
||||||
QuoteCount int `json:"quote_count,optional"`
|
|
||||||
Views int `json:"views,optional"`
|
|
||||||
Shares int `json:"shares,optional"`
|
|
||||||
Checkpoints []ThreadsPublishHealthCheckpoint `json:"checkpoints"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsPublishHealthData {
|
|
||||||
Summary ThreadsPublishHealthSummary `json:"summary"`
|
|
||||||
Pagination PaginationData `json:"pagination"`
|
|
||||||
List []ThreadsPublishHealthItem `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishSlotData {
|
|
||||||
Weekday int `json:"weekday"`
|
|
||||||
Time string `json:"time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishSourceRefData {
|
|
||||||
Type string `json:"type"`
|
|
||||||
PersonaID string `json:"persona_id,omitempty"`
|
|
||||||
CopyMissionID string `json:"copy_mission_id,omitempty"`
|
|
||||||
ScanPostID string `json:"scan_post_id,omitempty"`
|
|
||||||
ManualSeed string `json:"manual_seed,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishInventoryPolicyData {
|
|
||||||
AccountID string `json:"account_id"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
TargetDailyCount int `json:"target_daily_count"`
|
|
||||||
LowStockThreshold int `json:"low_stock_threshold"`
|
|
||||||
LookaheadDays int `json:"lookahead_days"`
|
|
||||||
Timezone string `json:"timezone"`
|
|
||||||
Slots []PublishSlotData `json:"slots"`
|
|
||||||
SourceRefs []PublishSourceRefData `json:"source_refs"`
|
|
||||||
UpdateAt int64 `json:"update_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
UpsertPublishInventoryPolicyReq {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
TargetDailyCount int `json:"target_daily_count,optional"`
|
|
||||||
LowStockThreshold int `json:"low_stock_threshold,optional"`
|
|
||||||
LookaheadDays int `json:"lookahead_days,optional"`
|
|
||||||
Timezone string `json:"timezone,optional"`
|
|
||||||
Slots []PublishSlotData `json:"slots,optional"`
|
|
||||||
SourceRefs []PublishSourceRefData `json:"source_refs,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
UpsertPublishInventoryPolicyHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
UpsertPublishInventoryPolicyReq
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPublishInventoryRefillJobReq {
|
|
||||||
Count int `json:"count,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPublishInventoryRefillJobHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
StartPublishInventoryRefillJobReq
|
|
||||||
}
|
|
||||||
|
|
||||||
StartPublishInventoryRefillJobData {
|
|
||||||
JobID string `json:"job_id"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishSlotInsightData {
|
|
||||||
Weekday int `json:"weekday"`
|
|
||||||
Time string `json:"time"`
|
|
||||||
AvgLikes1h int `json:"avg_likes_1h"`
|
|
||||||
AvgReplies1h int `json:"avg_replies_1h"`
|
|
||||||
AvgLikes24h int `json:"avg_likes_24h"`
|
|
||||||
AvgReplies24h int `json:"avg_replies_24h"`
|
|
||||||
AvgLikes7d int `json:"avg_likes_7d"`
|
|
||||||
AvgReplies7d int `json:"avg_replies_7d"`
|
|
||||||
SampleSize int `json:"sample_size"`
|
|
||||||
Recommendation string `json:"recommendation"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishSlotInsightsData {
|
|
||||||
AccountID string `json:"account_id"`
|
|
||||||
Timezone string `json:"timezone"`
|
|
||||||
Slots []PublishSlotInsightData `json:"slots"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishGuardPolicyData {
|
|
||||||
AccountID string `json:"account_id"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
MaxDailyPosts int `json:"max_daily_posts"`
|
|
||||||
MinIntervalMinutes int `json:"min_interval_minutes"`
|
|
||||||
ConsecutiveFailurePauseLimit int `json:"consecutive_failure_pause_limit"`
|
|
||||||
Paused bool `json:"paused"`
|
|
||||||
PausedReason string `json:"paused_reason,omitempty"`
|
|
||||||
UpdateAt int64 `json:"update_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
UpsertPublishGuardPolicyReq {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
MaxDailyPosts int `json:"max_daily_posts,optional"`
|
|
||||||
MinIntervalMinutes int `json:"min_interval_minutes,optional"`
|
|
||||||
ConsecutiveFailurePauseLimit int `json:"consecutive_failure_pause_limit,optional"`
|
|
||||||
Paused *bool `json:"paused,optional"`
|
|
||||||
PausedReason string `json:"paused_reason,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
UpsertPublishGuardPolicyHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
UpsertPublishGuardPolicyReq
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishQueueEventData {
|
|
||||||
ID string `json:"id"`
|
|
||||||
QueueID string `json:"queue_id"`
|
|
||||||
EventType string `json:"event_type"`
|
|
||||||
FromStatus string `json:"from_status,omitempty"`
|
|
||||||
ToStatus string `json:"to_status,omitempty"`
|
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
CreateAt int64 `json:"create_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishQueueEventsData {
|
|
||||||
List []PublishQueueEventData `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishAlertData {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Severity string `json:"severity"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
QueueID string `json:"queue_id,omitempty"`
|
|
||||||
AccountID string `json:"account_id"`
|
|
||||||
CreateAt int64 `json:"create_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishAlertsData {
|
|
||||||
List []PublishAlertData `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishQueueRangeQuery {
|
|
||||||
StartAt int64 `form:"startAt,optional"`
|
|
||||||
EndAt int64 `form:"endAt,optional"`
|
|
||||||
Status string `form:"status,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishQueueRangeHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
PublishQueueRangeQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishDashboardAccountSummary {
|
|
||||||
AccountID string `json:"account_id"`
|
|
||||||
AccountName string `json:"account_name"`
|
|
||||||
PendingScheduled int64 `json:"pending_scheduled"`
|
|
||||||
FailedCount int64 `json:"failed_count"`
|
|
||||||
Published7d int64 `json:"published_7d"`
|
|
||||||
TotalLikes7d int `json:"total_likes_7d"`
|
|
||||||
TotalReplies7d int `json:"total_replies_7d"`
|
|
||||||
Paused bool `json:"paused"`
|
|
||||||
BestSlot string `json:"best_slot,omitempty"`
|
|
||||||
LowSlot string `json:"low_slot,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishDashboardSummaryData {
|
|
||||||
List []PublishDashboardAccountSummary `json:"list"`
|
|
||||||
TotalPending int64 `json:"total_pending"`
|
|
||||||
TotalFailed int64 `json:"total_failed"`
|
|
||||||
TotalPublished7d int64 `json:"total_published_7d"`
|
|
||||||
TotalLikes7d int `json:"total_likes_7d"`
|
|
||||||
TotalReplies7d int `json:"total_replies_7d"`
|
|
||||||
}
|
|
||||||
|
|
||||||
GenerateOwnPostReplyDraftReq {
|
|
||||||
MediaID string `json:"media_id"`
|
|
||||||
PersonaID string `json:"persona_id,optional"`
|
|
||||||
ReplyToID string `json:"reply_to_id,optional"`
|
|
||||||
PostText string `json:"post_text,optional"`
|
|
||||||
ReplyText string `json:"reply_text,optional"`
|
|
||||||
ThreadPostText string `json:"thread_post_text,optional"`
|
|
||||||
ParentReplyText string `json:"parent_reply_text,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
GenerateOwnPostReplyDraftHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
GenerateOwnPostReplyDraftReq
|
|
||||||
}
|
|
||||||
|
|
||||||
GenerateOwnPostReplyDraftData {
|
|
||||||
Text string `json:"text"`
|
|
||||||
}
|
|
||||||
|
|
||||||
MentionInboxItemData {
|
|
||||||
MediaID string `json:"media_id"`
|
|
||||||
AuthorUsername string `json:"author_username,omitempty"`
|
|
||||||
Text string `json:"text,omitempty"`
|
|
||||||
Permalink string `json:"permalink,omitempty"`
|
|
||||||
Shortcode string `json:"shortcode,omitempty"`
|
|
||||||
Timestamp string `json:"timestamp,omitempty"`
|
|
||||||
MediaType string `json:"media_type,omitempty"`
|
|
||||||
IsReply bool `json:"is_reply"`
|
|
||||||
IsQuotePost bool `json:"is_quote_post"`
|
|
||||||
HasReplies bool `json:"has_replies"`
|
|
||||||
RootPostID string `json:"root_post_id,omitempty"`
|
|
||||||
ParentID string `json:"parent_id,omitempty"`
|
|
||||||
ThreadPostText string `json:"thread_post_text,omitempty"`
|
|
||||||
ParentReplyText string `json:"parent_reply_text,omitempty"`
|
|
||||||
ReplyReady bool `json:"reply_ready"`
|
|
||||||
ReplyReadyMsg string `json:"reply_ready_msg,omitempty"`
|
|
||||||
SyncedAt int64 `json:"synced_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
SyncMentionInboxReq {
|
|
||||||
Limit int `json:"limit,optional"`
|
|
||||||
MaxPages int `json:"max_pages,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
SyncMentionInboxHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
SyncMentionInboxReq
|
|
||||||
}
|
|
||||||
|
|
||||||
SyncMentionInboxData {
|
|
||||||
Synced int `json:"synced"`
|
|
||||||
Ready int `json:"ready"`
|
|
||||||
Total int64 `json:"total"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListMentionInboxQuery {
|
|
||||||
Page int `form:"page,optional"`
|
|
||||||
PageSize int `form:"pageSize,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListMentionInboxHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
ListMentionInboxQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
ListMentionInboxData {
|
|
||||||
List []MentionInboxItemData `json:"list"`
|
|
||||||
Pagination PaginationData `json:"pagination"`
|
|
||||||
ReadyTotal int64 `json:"ready_total"`
|
|
||||||
NewestSyncedAt int64 `json:"newest_synced_at,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishMentionReplyReq {
|
|
||||||
Text string `json:"text"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishMentionReplyPath {
|
|
||||||
ID string `path:"id" validate:"required"`
|
|
||||||
MediaID string `path:"media_id" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishMentionReplyHandlerReq {
|
|
||||||
PublishMentionReplyPath
|
|
||||||
PublishMentionReplyReq
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishMentionReplyData {
|
|
||||||
MediaID string `json:"media_id,omitempty"`
|
|
||||||
Permalink string `json:"permalink,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
GenerateOwnPostFormulaReq {
|
|
||||||
MediaID string `json:"media_id"`
|
|
||||||
PersonaID string `json:"persona_id,optional"`
|
|
||||||
PostText string `json:"post_text,optional"`
|
|
||||||
TopicTag string `json:"topic_tag,optional"`
|
|
||||||
MediaType string `json:"media_type,optional"`
|
|
||||||
Timestamp string `json:"timestamp,optional"`
|
|
||||||
LikeCount int `json:"like_count,optional"`
|
|
||||||
ReplyCount int `json:"reply_count,optional"`
|
|
||||||
Views int `json:"views,optional"`
|
|
||||||
RepostCount int `json:"repost_count,optional"`
|
|
||||||
QuoteCount int `json:"quote_count,optional"`
|
|
||||||
Shares int `json:"shares,optional"`
|
|
||||||
Force bool `json:"force,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
GenerateOwnPostFormulaHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
GenerateOwnPostFormulaReq
|
|
||||||
}
|
|
||||||
|
|
||||||
OwnPostFormulaReviewData {
|
|
||||||
MediaID string `json:"media_id"`
|
|
||||||
ReviewedAt int64 `json:"reviewed_at"`
|
|
||||||
FromCache bool `json:"from_cache,omitempty"`
|
|
||||||
Summary string `json:"summary"`
|
|
||||||
Wins []string `json:"wins"`
|
|
||||||
Improvements []string `json:"improvements"`
|
|
||||||
Formula string `json:"formula"`
|
|
||||||
PostTemplate string `json:"post_template"`
|
|
||||||
HookPattern string `json:"hook_pattern"`
|
|
||||||
Structure string `json:"structure"`
|
|
||||||
ReplicationTips []string `json:"replication_tips"`
|
|
||||||
Avoid []string `json:"avoid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
GenerateOwnPostFormulaData {
|
|
||||||
OwnPostFormulaReviewData
|
|
||||||
}
|
|
||||||
|
|
||||||
ListOwnPostFormulasHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
}
|
|
||||||
|
|
||||||
ListOwnPostFormulasData {
|
|
||||||
List []OwnPostFormulaReviewData `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadsDiagnosticsData {
|
|
||||||
AccountID string `json:"account_id"`
|
|
||||||
CheckedAt int64 `json:"checked_at"`
|
|
||||||
ApiConnected bool `json:"api_connected"`
|
|
||||||
TokenExpiresAt int64 `json:"token_expires_at,omitempty"`
|
|
||||||
Scopes []string `json:"scopes"`
|
|
||||||
Items []ThreadsAPISmokeTestItem `json:"items"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentFormulaSourceMetricsData {
|
|
||||||
LikeCount int `json:"like_count,omitempty"`
|
|
||||||
ReplyCount int `json:"reply_count,omitempty"`
|
|
||||||
Views int `json:"views,omitempty"`
|
|
||||||
RepostCount int `json:"repost_count,omitempty"`
|
|
||||||
QuoteCount int `json:"quote_count,omitempty"`
|
|
||||||
Shares int `json:"shares,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentFormulaData {
|
|
||||||
ID string `json:"id"`
|
|
||||||
AccountID string `json:"account_id"`
|
|
||||||
Label string `json:"label"`
|
|
||||||
SourceType string `json:"source_type"`
|
|
||||||
SourceRef string `json:"source_ref,omitempty"`
|
|
||||||
SourcePostText string `json:"source_post_text,omitempty"`
|
|
||||||
SourceAuthor string `json:"source_author,omitempty"`
|
|
||||||
SourcePermalink string `json:"source_permalink,omitempty"`
|
|
||||||
SourceMetrics *ContentFormulaSourceMetricsData `json:"source_metrics,omitempty"`
|
|
||||||
Summary string `json:"summary"`
|
|
||||||
Wins []string `json:"wins,omitempty"`
|
|
||||||
Improvements []string `json:"improvements,omitempty"`
|
|
||||||
Formula string `json:"formula"`
|
|
||||||
PostTemplate string `json:"post_template"`
|
|
||||||
HookPattern string `json:"hook_pattern"`
|
|
||||||
Structure string `json:"structure"`
|
|
||||||
ReplicationTips []string `json:"replication_tips,omitempty"`
|
|
||||||
Avoid []string `json:"avoid,omitempty"`
|
|
||||||
Tags []string `json:"tags,omitempty"`
|
|
||||||
CreateAt int64 `json:"create_at"`
|
|
||||||
UpdateAt int64 `json:"update_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListContentFormulasReq {
|
|
||||||
Page int64 `form:"page,optional"`
|
|
||||||
PageSize int64 `form:"pageSize,optional"`
|
|
||||||
SourceType string `form:"source_type,optional"`
|
|
||||||
Tag string `form:"tag,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ListContentFormulasHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
ListContentFormulasReq
|
|
||||||
}
|
|
||||||
|
|
||||||
ListContentFormulasData {
|
|
||||||
Pagination PaginationData `json:"pagination"`
|
|
||||||
List []ContentFormulaData `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentFormulaItemPath {
|
|
||||||
ID string `path:"id" validate:"required"`
|
|
||||||
FormulaID string `path:"formulaId" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
GetContentFormulaHandlerReq {
|
|
||||||
ContentFormulaItemPath
|
|
||||||
}
|
|
||||||
|
|
||||||
AnalyzeContentFormulaReq {
|
|
||||||
SourceType string `json:"source_type" validate:"required"`
|
|
||||||
PersonaID string `json:"persona_id,optional"`
|
|
||||||
PostText string `json:"post_text,optional"`
|
|
||||||
MediaID string `json:"media_id,optional"`
|
|
||||||
ScanPostID string `json:"scan_post_id,optional"`
|
|
||||||
Keyword string `json:"keyword,optional"`
|
|
||||||
SaveLabel string `json:"save_label,optional"`
|
|
||||||
AuthorName string `json:"author_name,optional"`
|
|
||||||
Permalink string `json:"permalink,optional"`
|
|
||||||
LikeCount int `json:"like_count,optional"`
|
|
||||||
ReplyCount int `json:"reply_count,optional"`
|
|
||||||
Views int `json:"views,optional"`
|
|
||||||
RepostCount int `json:"repost_count,optional"`
|
|
||||||
QuoteCount int `json:"quote_count,optional"`
|
|
||||||
Shares int `json:"shares,optional"`
|
|
||||||
ForceRefresh bool `json:"force_refresh,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
AnalyzeContentFormulaHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
AnalyzeContentFormulaReq
|
|
||||||
}
|
|
||||||
|
|
||||||
AnalyzeContentFormulaData {
|
|
||||||
Formula ContentFormulaData `json:"formula"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchContentFormulaPostsReq {
|
|
||||||
Keyword string `json:"keyword" validate:"required"`
|
|
||||||
SearchType string `json:"search_type,optional"`
|
|
||||||
Limit int `json:"limit,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchContentFormulaPostsHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
SearchContentFormulaPostsReq
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentFormulaSearchPostData {
|
|
||||||
Text string `json:"text"`
|
|
||||||
Author string `json:"author"`
|
|
||||||
Permalink string `json:"permalink,omitempty"`
|
|
||||||
MediaID string `json:"media_id,omitempty"`
|
|
||||||
LikeCount int `json:"like_count"`
|
|
||||||
ReplyCount int `json:"reply_count"`
|
|
||||||
EngagementScore int `json:"engagement_score"`
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchContentFormulaPostsData {
|
|
||||||
List []ContentFormulaSearchPostData `json:"list"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchContentFormulaReq {
|
|
||||||
Label *string `json:"label,optional"`
|
|
||||||
Formula *string `json:"formula,optional"`
|
|
||||||
PostTemplate *string `json:"post_template,optional"`
|
|
||||||
HookPattern *string `json:"hook_pattern,optional"`
|
|
||||||
Structure *string `json:"structure,optional"`
|
|
||||||
Tags []string `json:"tags,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchContentFormulaHandlerReq {
|
|
||||||
ContentFormulaItemPath
|
|
||||||
PatchContentFormulaReq
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteContentFormulaData {
|
|
||||||
FormulaID string `json:"formula_id"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ImportOwnPostFormulaReq {
|
|
||||||
MediaID string `json:"media_id" validate:"required"`
|
|
||||||
Label string `json:"label,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ImportOwnPostFormulaHandlerReq {
|
|
||||||
ThreadsAccountPath
|
|
||||||
ImportOwnPostFormulaReq
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@server(
|
@server(
|
||||||
|
|
@ -871,9 +159,6 @@ service gateway {
|
||||||
@handler updateThreadsAccount
|
@handler updateThreadsAccount
|
||||||
patch /:id (UpdateThreadsAccountHandlerReq) returns (ThreadsAccountData)
|
patch /:id (UpdateThreadsAccountHandlerReq) returns (ThreadsAccountData)
|
||||||
|
|
||||||
@handler deleteThreadsAccount
|
|
||||||
delete /:id (ThreadsAccountPath) returns (DeleteThreadsAccountData)
|
|
||||||
|
|
||||||
@handler activateThreadsAccount
|
@handler activateThreadsAccount
|
||||||
post /:id/activate (ThreadsAccountPath)
|
post /:id/activate (ThreadsAccountPath)
|
||||||
|
|
||||||
|
|
@ -894,135 +179,4 @@ service gateway {
|
||||||
|
|
||||||
@handler listThreadsAccountAiProviderModels
|
@handler listThreadsAccountAiProviderModels
|
||||||
post /:id/ai-settings/providers/:provider/models (ThreadsAccountAiProviderModelsHandlerReq) returns (ThreadsAccountAiProviderModelsData)
|
post /:id/ai-settings/providers/:provider/models (ThreadsAccountAiProviderModelsHandlerReq) returns (ThreadsAccountAiProviderModelsData)
|
||||||
|
|
||||||
@handler startThreadsOauth
|
|
||||||
get /oauth/start (StartThreadsOAuthQuery) returns (StartThreadsOAuthData)
|
|
||||||
|
|
||||||
@handler listThreadsOauthLogs
|
|
||||||
get /oauth/logs returns (ListThreadsOAuthLogsData)
|
|
||||||
|
|
||||||
@handler getThreadsOauthConfig
|
|
||||||
get /oauth/config returns (ThreadsOAuthConfigData)
|
|
||||||
|
|
||||||
@handler disconnectThreadsApi
|
|
||||||
post /:id/api/disconnect (ThreadsAccountPath) returns (ThreadsAccountData)
|
|
||||||
|
|
||||||
@handler runThreadsApiSmokeTest
|
|
||||||
post /:id/api/smoke-test (ThreadsAccountPath) returns (RunThreadsAPISmokeTestData)
|
|
||||||
|
|
||||||
@handler runThreadsApiPlayground
|
|
||||||
post /:id/api/playground (RunThreadsAPIPlaygroundHandlerReq) returns (ThreadsAPIPlaygroundData)
|
|
||||||
|
|
||||||
@handler listThreadsPostPerformance
|
|
||||||
get /:id/post-performance (ListThreadsPostPerformanceHandlerReq) returns (ThreadsPostPerformanceData)
|
|
||||||
|
|
||||||
@handler createPublishQueueItem
|
|
||||||
post /:id/publish-queue (CreatePublishQueueHandlerReq) returns (PublishQueueItemData)
|
|
||||||
|
|
||||||
@handler listPublishQueueItems
|
|
||||||
get /:id/publish-queue (ListPublishQueueHandlerReq) returns (PublishQueueListData)
|
|
||||||
|
|
||||||
@handler getPublishQueueItem
|
|
||||||
get /:id/publish-queue/:qid (PublishQueueItemPath) returns (PublishQueueItemData)
|
|
||||||
|
|
||||||
@handler patchPublishQueueItem
|
|
||||||
patch /:id/publish-queue/:qid (PatchPublishQueueHandlerReq) returns (PublishQueueItemData)
|
|
||||||
|
|
||||||
@handler cancelPublishQueueItem
|
|
||||||
post /:id/publish-queue/:qid/cancel (PublishQueueItemPath) returns (PublishQueueItemData)
|
|
||||||
|
|
||||||
@handler deletePublishQueueItem
|
|
||||||
delete /:id/publish-queue/:qid (PublishQueueItemPath)
|
|
||||||
|
|
||||||
@handler listThreadsPublishHealth
|
|
||||||
get /:id/publish-health (ListThreadsPublishHealthHandlerReq) returns (ThreadsPublishHealthData)
|
|
||||||
|
|
||||||
@handler getPublishInventoryPolicy
|
|
||||||
get /:id/publish-inventory-policy (ThreadsAccountPath) returns (PublishInventoryPolicyData)
|
|
||||||
|
|
||||||
@handler upsertPublishInventoryPolicy
|
|
||||||
put /:id/publish-inventory-policy (UpsertPublishInventoryPolicyHandlerReq) returns (PublishInventoryPolicyData)
|
|
||||||
|
|
||||||
@handler startPublishInventoryRefillJob
|
|
||||||
post /:id/publish-inventory/refill-jobs (StartPublishInventoryRefillJobHandlerReq) returns (StartPublishInventoryRefillJobData)
|
|
||||||
|
|
||||||
@handler getPublishSlotInsights
|
|
||||||
get /:id/publish-slot-insights (ThreadsAccountPath) returns (PublishSlotInsightsData)
|
|
||||||
|
|
||||||
@handler getPublishGuardPolicy
|
|
||||||
get /:id/publish-guard-policy (ThreadsAccountPath) returns (PublishGuardPolicyData)
|
|
||||||
|
|
||||||
@handler upsertPublishGuardPolicy
|
|
||||||
put /:id/publish-guard-policy (UpsertPublishGuardPolicyHandlerReq) returns (PublishGuardPolicyData)
|
|
||||||
|
|
||||||
@handler resumePublishGuard
|
|
||||||
post /:id/publish-guard/resume (ThreadsAccountPath) returns (PublishGuardPolicyData)
|
|
||||||
|
|
||||||
@handler retryPublishQueueItem
|
|
||||||
post /:id/publish-queue/:qid/retry (PublishQueueItemPath) returns (PublishQueueItemData)
|
|
||||||
|
|
||||||
@handler listPublishQueueEvents
|
|
||||||
get /:id/publish-queue/:qid/events (PublishQueueItemPath) returns (PublishQueueEventsData)
|
|
||||||
|
|
||||||
@handler listPublishAlerts
|
|
||||||
get /:id/publish-alerts (ThreadsAccountPath) returns (PublishAlertsData)
|
|
||||||
|
|
||||||
@handler listPublishQueueRange
|
|
||||||
get /:id/publish-calendar (PublishQueueRangeHandlerReq) returns (PublishQueueListData)
|
|
||||||
|
|
||||||
@handler getThreadsDiagnostics
|
|
||||||
get /:id/diagnostics (ThreadsAccountPath) returns (ThreadsDiagnosticsData)
|
|
||||||
|
|
||||||
@handler generateOwnPostReplyDraft
|
|
||||||
post /:id/own-post-reply-draft (GenerateOwnPostReplyDraftHandlerReq) returns (GenerateOwnPostReplyDraftData)
|
|
||||||
|
|
||||||
@handler syncMentionInbox
|
|
||||||
post /:id/mention-inbox/sync (SyncMentionInboxHandlerReq) returns (SyncMentionInboxData)
|
|
||||||
|
|
||||||
@handler listMentionInbox
|
|
||||||
get /:id/mention-inbox (ListMentionInboxHandlerReq) returns (ListMentionInboxData)
|
|
||||||
|
|
||||||
@handler publishMentionReply
|
|
||||||
post /:id/mention-inbox/:media_id/reply (PublishMentionReplyHandlerReq) returns (PublishMentionReplyData)
|
|
||||||
|
|
||||||
@handler generateOwnPostFormula
|
|
||||||
post /:id/own-post-formula (GenerateOwnPostFormulaHandlerReq) returns (GenerateOwnPostFormulaData)
|
|
||||||
|
|
||||||
@handler listOwnPostFormulas
|
|
||||||
get /:id/own-post-formulas (ListOwnPostFormulasHandlerReq) returns (ListOwnPostFormulasData)
|
|
||||||
|
|
||||||
@handler getPublishDashboardSummary
|
|
||||||
get /publish-dashboard-summary returns (PublishDashboardSummaryData)
|
|
||||||
|
|
||||||
@handler listContentFormulas
|
|
||||||
get /:id/content-formulas (ListContentFormulasHandlerReq) returns (ListContentFormulasData)
|
|
||||||
|
|
||||||
@handler getContentFormula
|
|
||||||
get /:id/content-formulas/:formulaId (GetContentFormulaHandlerReq) returns (ContentFormulaData)
|
|
||||||
|
|
||||||
@handler analyzeContentFormula
|
|
||||||
post /:id/content-formulas/analyze (AnalyzeContentFormulaHandlerReq) returns (AnalyzeContentFormulaData)
|
|
||||||
|
|
||||||
@handler searchContentFormulaPosts
|
|
||||||
post /:id/content-formulas/search-posts (SearchContentFormulaPostsHandlerReq) returns (SearchContentFormulaPostsData)
|
|
||||||
|
|
||||||
@handler patchContentFormula
|
|
||||||
patch /:id/content-formulas/:formulaId (PatchContentFormulaHandlerReq) returns (ContentFormulaData)
|
|
||||||
|
|
||||||
@handler deleteContentFormula
|
|
||||||
delete /:id/content-formulas/:formulaId (GetContentFormulaHandlerReq) returns (DeleteContentFormulaData)
|
|
||||||
|
|
||||||
@handler importOwnPostToContentFormula
|
|
||||||
post /:id/content-formulas/import-own-post (ImportOwnPostFormulaHandlerReq) returns (AnalyzeContentFormulaData)
|
|
||||||
}
|
|
||||||
|
|
||||||
@server(
|
|
||||||
group: threads_account
|
|
||||||
prefix: /api/v1/threads-accounts
|
|
||||||
tags: "ThreadsAccount"
|
|
||||||
summary: "Threads OAuth callback (public redirect from Meta)."
|
|
||||||
)
|
|
||||||
service gateway {
|
|
||||||
@handler threadsOauthCallback
|
|
||||||
get /oauth/callback (ThreadsOAuthCallbackQuery)
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
module haixun-backend
|
module haixun-backend
|
||||||
|
|
||||||
go 1.23
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-playground/validator/v10 v10.27.0
|
github.com/go-playground/validator/v10 v10.27.0
|
||||||
github.com/go-shiori/go-readability v0.0.0-20251205110129-5db1dc9836f0
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/redis/go-redis/v9 v9.14.0
|
github.com/redis/go-redis/v9 v9.14.0
|
||||||
|
|
@ -15,8 +14,6 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
|
||||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
|
@ -27,8 +24,6 @@ require (
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c // indirect
|
|
||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
|
||||||
github.com/golang/snappy v1.0.0 // indirect
|
github.com/golang/snappy v1.0.0 // indirect
|
||||||
github.com/grafana/pyroscope-go v1.2.7 // indirect
|
github.com/grafana/pyroscope-go v1.2.7 // indirect
|
||||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect
|
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
|
||||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
|
||||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
|
||||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
|
@ -34,12 +30,6 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c h1:wpkoddUomPfHiOziHZixGO5ZBS73cKqVzZipfrLmO1w=
|
|
||||||
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c/go.mod h1:oVDCh3qjJMLVUSILBRwrm+Bc6RNXGZYtoh9xdvf1ffM=
|
|
||||||
github.com/go-shiori/go-readability v0.0.0-20251205110129-5db1dc9836f0 h1:A3B75Yp163FAIf9nLlFMl4pwIj+T3uKxfI7mbvvY2Ls=
|
|
||||||
github.com/go-shiori/go-readability v0.0.0-20251205110129-5db1dc9836f0/go.mod h1:suxK0Wpz4BM3/2+z1mnOVTIWHDiMCIOGoKDCRumSsk0=
|
|
||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
|
|
||||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||||
|
|
@ -71,7 +61,6 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
|
||||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
|
|
@ -94,14 +83,10 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE=
|
github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE=
|
||||||
github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
|
||||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
|
@ -109,7 +94,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
|
@ -157,35 +141,16 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
|
||||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|
||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
|
||||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|
@ -194,42 +159,20 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
|
||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
|
||||||
|
|
|
||||||
|
|
@ -36,18 +36,6 @@ func EnsureAdminMember(ctx context.Context, repo domrepo.Repository, opts AdminO
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
existing.Roles = []string{"admin"}
|
existing.Roles = []string{"admin"}
|
||||||
|
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(existing.PasswordHash), []byte(opts.Password)); err != nil {
|
|
||||||
hash, hashErr := bcrypt.GenerateFromPassword([]byte(opts.Password), bcrypt.DefaultCost)
|
|
||||||
if hashErr != nil {
|
|
||||||
return nil, false, app.For(code.Member).SysInternal("rehash password failed").WithCause(hashErr)
|
|
||||||
}
|
|
||||||
if updateErr := repo.UpdatePassword(ctx, tenantID, existing.UID, string(hash)); updateErr != nil {
|
|
||||||
return nil, false, updateErr
|
|
||||||
}
|
|
||||||
existing.PasswordHash = string(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
return existing, false, nil
|
return existing, false, nil
|
||||||
}
|
}
|
||||||
if e := app.FromError(err); e == nil || e.Category() != code.ResNotFound {
|
if e := app.FromError(err); e == nil || e.Category() != code.ResNotFound {
|
||||||
|
|
|
||||||
|
|
@ -68,16 +68,6 @@ func (m *memoryMemberRepo) SetActiveThreadsAccountID(_ context.Context, tenantID
|
||||||
return app.For(code.Member).ResNotFound("member not found")
|
return app.For(code.Member).ResNotFound("member not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryMemberRepo) UpdatePassword(_ context.Context, tenantID, uid, passwordHash string) error {
|
|
||||||
for _, item := range m.byEmail {
|
|
||||||
if item.TenantID == tenantID && item.UID == uid {
|
|
||||||
item.PasswordHash = passwordHash
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return app.For(code.Member).ResNotFound("member not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memoryMemberRepo) SetRoles(_ context.Context, tenantID, uid string, roles []string) error {
|
func (m *memoryMemberRepo) SetRoles(_ context.Context, tenantID, uid string, roles []string) error {
|
||||||
for _, item := range m.byEmail {
|
for _, item := range m.byEmail {
|
||||||
if item.TenantID == tenantID && item.UID == uid {
|
if item.TenantID == tenantID && item.UID == uid {
|
||||||
|
|
@ -133,72 +123,4 @@ func TestEnsureAdminMemberUpgradesExisting(t *testing.T) {
|
||||||
if len(member.Roles) != 1 || member.Roles[0] != "admin" {
|
if len(member.Roles) != 1 || member.Roles[0] != "admin" {
|
||||||
t.Fatalf("roles=%v", member.Roles)
|
t.Fatalf("roles=%v", member.Roles)
|
||||||
}
|
}
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(member.PasswordHash), []byte("Admin-Pass-1!")); err != nil {
|
|
||||||
t.Fatalf("password should have been synced: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnsureAdminMemberSyncsPasswordWhenChanged(t *testing.T) {
|
|
||||||
oldHash, err := bcrypt.GenerateFromPassword([]byte("Old-Pass-1!"), bcrypt.DefaultCost)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
repo := &memoryMemberRepo{byEmail: map[string]*entity.Member{
|
|
||||||
memberKey("default", "admin@haixun.local"): {
|
|
||||||
TenantID: "default",
|
|
||||||
UID: "uid-1",
|
|
||||||
Email: "admin@haixun.local",
|
|
||||||
Roles: []string{"admin"},
|
|
||||||
PasswordHash: string(oldHash),
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
_, created, err := EnsureAdminMember(context.Background(), repo, AdminOptions{
|
|
||||||
TenantID: "default",
|
|
||||||
Email: "admin@haixun.local",
|
|
||||||
Password: "New-Pass-2@",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("EnsureAdminMember: %v", err)
|
|
||||||
}
|
|
||||||
if created {
|
|
||||||
t.Fatal("expected created=false")
|
|
||||||
}
|
|
||||||
member := repo.byEmail[memberKey("default", "admin@haixun.local")]
|
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(member.PasswordHash), []byte("Old-Pass-1!")); err == nil {
|
|
||||||
t.Fatal("password should have been updated, but old hash still matches")
|
|
||||||
}
|
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(member.PasswordHash), []byte("New-Pass-2@")); err != nil {
|
|
||||||
t.Fatalf("new password hash does not match: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnsureAdminMemberKeepsPasswordWhenSame(t *testing.T) {
|
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte("Same-Pass-1!"), bcrypt.DefaultCost)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
repo := &memoryMemberRepo{byEmail: map[string]*entity.Member{
|
|
||||||
memberKey("default", "admin@haixun.local"): {
|
|
||||||
TenantID: "default",
|
|
||||||
UID: "uid-1",
|
|
||||||
Email: "admin@haixun.local",
|
|
||||||
Roles: []string{"admin"},
|
|
||||||
PasswordHash: string(hash),
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
_, created, err := EnsureAdminMember(context.Background(), repo, AdminOptions{
|
|
||||||
TenantID: "default",
|
|
||||||
Email: "admin@haixun.local",
|
|
||||||
Password: "Same-Pass-1!",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("EnsureAdminMember: %v", err)
|
|
||||||
}
|
|
||||||
if created {
|
|
||||||
t.Fatal("expected created=false")
|
|
||||||
}
|
|
||||||
member := repo.byEmail[memberKey("default", "admin@haixun.local")]
|
|
||||||
if member.PasswordHash != string(hash) {
|
|
||||||
t.Fatal("password hash should not change when password is the same")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoadInfraEnv reads local env files into process env (only keys not already set).
|
|
||||||
// Search order: HAIXUN_ENV_FILE, deploy/.env.dev (for native dev run).
|
|
||||||
func LoadInfraEnv() {
|
|
||||||
if path := strings.TrimSpace(os.Getenv("HAIXUN_ENV_FILE")); path != "" {
|
|
||||||
_ = loadEnvFile(path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
candidates := []string{
|
|
||||||
filepath.Join(cwd, "deploy", ".env.dev"),
|
|
||||||
filepath.Join(cwd, "..", "deploy", ".env.dev"),
|
|
||||||
filepath.Join(cwd, "..", "..", "deploy", ".env.dev"),
|
|
||||||
}
|
|
||||||
for _, path := range candidates {
|
|
||||||
if loadEnvFile(path) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadEnvFile(path string) bool {
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(f)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := strings.TrimSpace(scanner.Text())
|
|
||||||
if line == "" || strings.HasPrefix(line, "#") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key, value, ok := strings.Cut(line, "=")
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key = strings.TrimSpace(key)
|
|
||||||
if key == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, exists := os.LookupEnv(key); exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
value = strings.TrimSpace(value)
|
|
||||||
if len(value) >= 2 {
|
|
||||||
if (value[0] == '"' && value[len(value)-1] == '"') ||
|
|
||||||
(value[0] == '\'' && value[len(value)-1] == '\'') {
|
|
||||||
value = value[1 : len(value)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = os.Setenv(key, value)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLoadEnvFile(t *testing.T) {
|
|
||||||
dir := t.TempDir()
|
|
||||||
path := filepath.Join(dir, ".env")
|
|
||||||
content := `# comment
|
|
||||||
HAIXUN_JWT_ACCESS_SECRET=from-file
|
|
||||||
THREADS_APP_ID="1566509781118049"
|
|
||||||
`
|
|
||||||
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Setenv("HAIXUN_JWT_REFRESH_SECRET", "preset")
|
|
||||||
unset := []string{"HAIXUN_JWT_ACCESS_SECRET", "THREADS_APP_ID"}
|
|
||||||
for _, key := range unset {
|
|
||||||
_ = os.Unsetenv(key)
|
|
||||||
}
|
|
||||||
if !loadEnvFile(path) {
|
|
||||||
t.Fatal("loadEnvFile returned false")
|
|
||||||
}
|
|
||||||
if got := os.Getenv("HAIXUN_JWT_ACCESS_SECRET"); got != "from-file" {
|
|
||||||
t.Fatalf("access secret = %q", got)
|
|
||||||
}
|
|
||||||
if got := os.Getenv("HAIXUN_JWT_REFRESH_SECRET"); got != "preset" {
|
|
||||||
t.Fatalf("refresh secret should stay preset, got %q", got)
|
|
||||||
}
|
|
||||||
if got := os.Getenv("THREADS_APP_ID"); got != "1566509781118049" {
|
|
||||||
t.Fatalf("threads app id = %q", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,31 +8,19 @@ import (
|
||||||
libcrypto "haixun-backend/internal/library/crypto"
|
libcrypto "haixun-backend/internal/library/crypto"
|
||||||
libmongo "haixun-backend/internal/library/mongo"
|
libmongo "haixun-backend/internal/library/mongo"
|
||||||
brandrepo "haixun-backend/internal/model/brand/repository"
|
brandrepo "haixun-backend/internal/model/brand/repository"
|
||||||
contentformularepo "haixun-backend/internal/model/content_formula/repository"
|
|
||||||
cmatrixrepo "haixun-backend/internal/model/content_matrix/repository"
|
cmatrixrepo "haixun-backend/internal/model/content_matrix/repository"
|
||||||
contentopsrepo "haixun-backend/internal/model/content_ops/repository"
|
|
||||||
copydraftrepo "haixun-backend/internal/model/copy_draft/repository"
|
copydraftrepo "haixun-backend/internal/model/copy_draft/repository"
|
||||||
copymissionrepo "haixun-backend/internal/model/copy_mission/repository"
|
copymissionrepo "haixun-backend/internal/model/copy_mission/repository"
|
||||||
crmcontactrepo "haixun-backend/internal/model/crm_contact/repository"
|
|
||||||
jobrepo "haixun-backend/internal/model/job/repository"
|
jobrepo "haixun-backend/internal/model/job/repository"
|
||||||
jobusecase "haixun-backend/internal/model/job/usecase"
|
|
||||||
kgrepo "haixun-backend/internal/model/knowledge_graph/repository"
|
kgrepo "haixun-backend/internal/model/knowledge_graph/repository"
|
||||||
memberrepo "haixun-backend/internal/model/member/repository"
|
memberrepo "haixun-backend/internal/model/member/repository"
|
||||||
mentioninboxrepo "haixun-backend/internal/model/mention_inbox/repository"
|
|
||||||
outreachdraftrepo "haixun-backend/internal/model/outreach_draft/repository"
|
outreachdraftrepo "haixun-backend/internal/model/outreach_draft/repository"
|
||||||
ownpostformularepo "haixun-backend/internal/model/own_post_formula/repository"
|
|
||||||
permissionrepo "haixun-backend/internal/model/permission/repository"
|
permissionrepo "haixun-backend/internal/model/permission/repository"
|
||||||
permissionuc "haixun-backend/internal/model/permission/usecase"
|
permissionuc "haixun-backend/internal/model/permission/usecase"
|
||||||
personarepo "haixun-backend/internal/model/persona/repository"
|
personarepo "haixun-backend/internal/model/persona/repository"
|
||||||
placementtopicrepo "haixun-backend/internal/model/placement_topic/repository"
|
placementtopicrepo "haixun-backend/internal/model/placement_topic/repository"
|
||||||
publishanalyticsrepo "haixun-backend/internal/model/publish_analytics/repository"
|
|
||||||
publishguardrepo "haixun-backend/internal/model/publish_guard/repository"
|
|
||||||
publishinventoryrepo "haixun-backend/internal/model/publish_inventory/repository"
|
|
||||||
publishqueuerepo "haixun-backend/internal/model/publish_queue/repository"
|
|
||||||
publishqueueeventrepo "haixun-backend/internal/model/publish_queue_event/repository"
|
|
||||||
scanpostrepo "haixun-backend/internal/model/scan_post/repository"
|
scanpostrepo "haixun-backend/internal/model/scan_post/repository"
|
||||||
settingrepo "haixun-backend/internal/model/setting/repository"
|
settingrepo "haixun-backend/internal/model/setting/repository"
|
||||||
stylepresetrepo "haixun-backend/internal/model/style_preset/repository"
|
|
||||||
threadsaccountrepo "haixun-backend/internal/model/threads_account/repository"
|
threadsaccountrepo "haixun-backend/internal/model/threads_account/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -109,17 +97,6 @@ func Init(ctx context.Context, cfg config.Config, opts InitOptions) (*InitReport
|
||||||
{"placement_topics", placementtopicrepo.NewMongoRepository(db).EnsureIndexes},
|
{"placement_topics", placementtopicrepo.NewMongoRepository(db).EnsureIndexes},
|
||||||
{"threads_accounts", threadsaccountrepo.NewMongoRepository(db).EnsureIndexes},
|
{"threads_accounts", threadsaccountrepo.NewMongoRepository(db).EnsureIndexes},
|
||||||
{"threads_account_secrets", threadsaccountrepo.NewSecretsMongoRepository(db, secretsCipher).EnsureIndexes},
|
{"threads_account_secrets", threadsaccountrepo.NewSecretsMongoRepository(db, secretsCipher).EnsureIndexes},
|
||||||
{"publish_analytics", publishanalyticsrepo.NewMongoRepository(db).EnsureIndexes},
|
|
||||||
{"publish_queue", publishqueuerepo.NewMongoRepository(db).EnsureIndexes},
|
|
||||||
{"publish_inventory", publishinventoryrepo.NewMongoRepository(db).EnsureIndexes},
|
|
||||||
{"publish_guard", publishguardrepo.NewMongoRepository(db).EnsureIndexes},
|
|
||||||
{"publish_queue_events", publishqueueeventrepo.NewMongoRepository(db).EnsureIndexes},
|
|
||||||
{"style_presets", stylepresetrepo.NewMongoRepository(db).EnsureIndexes},
|
|
||||||
{"own_post_formulas", ownpostformularepo.NewMongoRepository(db).EnsureIndexes},
|
|
||||||
{"content_formulas", contentformularepo.NewMongoRepository(db).EnsureIndexes},
|
|
||||||
{"content_ops", contentopsrepo.NewMongoRepository(db).EnsureIndexes},
|
|
||||||
{"mention_inbox", mentioninboxrepo.NewMongoRepository(db).EnsureIndexes},
|
|
||||||
{"crm_contacts", crmcontactrepo.NewMongoRepository(db).EnsureIndexes},
|
|
||||||
}
|
}
|
||||||
for _, repo := range repos {
|
for _, repo := range repos {
|
||||||
if err := repo.fn(ctx); err != nil {
|
if err := repo.fn(ctx); err != nil {
|
||||||
|
|
@ -128,26 +105,6 @@ func Init(ctx context.Context, cfg config.Config, opts InitOptions) (*InitReport
|
||||||
}
|
}
|
||||||
report.IndexesEnsured = true
|
report.IndexesEnsured = true
|
||||||
|
|
||||||
jobUseCase := jobusecase.NewUseCase(jobTemplateRepository, jobRunRepository, jobScheduleRepository, jobEventRepository, nil)
|
|
||||||
jobTemplates := []struct {
|
|
||||||
name string
|
|
||||||
fn func(context.Context) error
|
|
||||||
}{
|
|
||||||
{"demo", jobUseCase.EnsureDemoTemplate},
|
|
||||||
{"style_8d", jobUseCase.EnsureStyle8DTemplate},
|
|
||||||
{"expand_graph", jobUseCase.EnsureExpandGraphTemplate},
|
|
||||||
{"placement_scan", jobUseCase.EnsurePlacementScanTemplate},
|
|
||||||
{"scan_viral", jobUseCase.EnsureScanViralTemplate},
|
|
||||||
{"generate_outreach_draft", jobUseCase.EnsureGenerateOutreachDraftTemplate},
|
|
||||||
{"refresh_threads_token", jobUseCase.EnsureRefreshThreadsTokenTemplate},
|
|
||||||
{"publish_analytics", jobUseCase.EnsurePublishAnalyticsTemplate},
|
|
||||||
}
|
|
||||||
for _, template := range jobTemplates {
|
|
||||||
if err := template.fn(ctx); err != nil {
|
|
||||||
return nil, fmt.Errorf("seed %s job template: %w", template.name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
permissionUseCase := permissionuc.NewUseCase(permissionRepository, rolePermissionRepository)
|
permissionUseCase := permissionuc.NewUseCase(permissionRepository, rolePermissionRepository)
|
||||||
if err := permissionUseCase.EnsureDefaultPermissions(ctx); err != nil {
|
if err := permissionUseCase.EnsureDefaultPermissions(ctx); err != nil {
|
||||||
return nil, fmt.Errorf("seed permissions catalog: %w", err)
|
return nil, fmt.Errorf("seed permissions catalog: %w", err)
|
||||||
|
|
|
||||||
|
|
@ -52,37 +52,15 @@ type BraveConf struct {
|
||||||
APIKey string `json:",optional"`
|
APIKey string `json:",optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ThreadsOAuthConf struct {
|
|
||||||
AppID string `json:",optional"`
|
|
||||||
AppSecret string `json:",optional"`
|
|
||||||
RedirectURI string `json:",optional"`
|
|
||||||
SuccessRedirect string `json:",optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ThreadsTokenRefreshConf struct {
|
|
||||||
Enabled bool `json:",default=true"`
|
|
||||||
IntervalSeconds int `json:",default=3600"`
|
|
||||||
LeadDays int `json:",default=7"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ThreadsPublishQueueConf struct {
|
|
||||||
Enabled bool `json:",default=true"`
|
|
||||||
IntervalSeconds int `json:",default=60"`
|
|
||||||
BatchSize int `json:",default=20"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
rest.RestConf
|
rest.RestConf
|
||||||
Mongo MongoConf `json:",optional"`
|
Mongo MongoConf `json:",optional"`
|
||||||
Redis RedisConf `json:",optional"`
|
Redis RedisConf `json:",optional"`
|
||||||
Auth AuthConf `json:",optional"`
|
Auth AuthConf `json:",optional"`
|
||||||
Secrets SecretsConf `json:",optional"`
|
Secrets SecretsConf `json:",optional"`
|
||||||
InternalWorker InternalWorkerConf `json:",optional"`
|
InternalWorker InternalWorkerConf `json:",optional"`
|
||||||
JobWorker JobWorkerConf `json:",optional"`
|
JobWorker JobWorkerConf `json:",optional"`
|
||||||
JobScheduler JobSchedulerConf `json:",optional"`
|
JobScheduler JobSchedulerConf `json:",optional"`
|
||||||
JobReaper JobReaperConf `json:",optional"`
|
JobReaper JobReaperConf `json:",optional"`
|
||||||
Brave BraveConf `json:",optional"`
|
Brave BraveConf `json:",optional"`
|
||||||
ThreadsOAuth ThreadsOAuthConf `json:",optional"`
|
|
||||||
ThreadsTokenRefresh ThreadsTokenRefreshConf `json:",optional"`
|
|
||||||
ThreadsPublishQueue ThreadsPublishQueueConf `json:",optional"`
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ package copy_mission
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateCopyMissionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func CreateCopyMissionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ package copy_mission
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DeleteCopyMissionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func DeleteCopyMissionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package copy_mission
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DeleteCopyMissionMatrixDraftsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.DeleteCopyMissionMatrixDraftsHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := copy_mission.NewDeleteCopyMissionMatrixDraftsLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.DeleteCopyMissionMatrixDrafts(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package copy_mission
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExpandCopyMissionGraphHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.ExpandCopyMissionGraphHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := copy_mission.NewExpandCopyMissionGraphLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.ExpandCopyMissionGraph(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,11 +6,12 @@ package copy_mission
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateCopyMissionMatrixHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func GenerateCopyMissionMatrixHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package copy_mission
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetCopyMissionGraphHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.CopyMissionPath
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := copy_mission.NewGetCopyMissionGraphLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.GetCopyMissionGraph(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,11 +6,12 @@ package copy_mission
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetCopyMissionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func GetCopyMissionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ package copy_mission
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetCopyMissionScanScheduleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func GetCopyMissionScanScheduleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,19 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package copy_mission
|
package copy_mission
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InspireCopyMissionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func InspireCopyMissionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var req types.CopyMissionInspirationHandlerReq
|
var req types.PersonaCopyMissionsPath
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ package copy_mission
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ListCopyMissionCopyDraftsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func ListCopyMissionCopyDraftsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ package copy_mission
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ListCopyMissionScanPostsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func ListCopyMissionScanPostsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ package copy_mission
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ListCopyMissionsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func ListCopyMissionsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package copy_mission
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PatchCopyMissionAudienceSampleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.PatchAudienceSampleHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := copy_mission.NewPatchCopyMissionAudienceSampleLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.PatchCopyMissionAudienceSample(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package copy_mission
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PatchCopyMissionGraphNodesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.PatchCopyMissionGraphNodesHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := copy_mission.NewPatchCopyMissionGraphNodesLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.PatchCopyMissionGraphNodes(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package copy_mission
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PatchCopyMissionSimilarAccountHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.PatchSimilarAccountHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := copy_mission.NewPatchCopyMissionSimilarAccountLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.PatchCopyMissionSimilarAccount(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package copy_mission
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ScheduleCopyMissionDraftsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.ScheduleCopyMissionDraftsHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := copy_mission.NewScheduleCopyMissionDraftsLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.ScheduleCopyMissionDrafts(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,11 +6,12 @@ package copy_mission
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartCopyMissionAnalyzeJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func StartCopyMissionAnalyzeJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package copy_mission
|
package copy_mission
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartCopyMissionCopyDraftJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func StartCopyMissionCopyDraftJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package copy_mission
|
package copy_mission
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartCopyMissionMatrixJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func StartCopyMissionMatrixJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ package copy_mission
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartCopyMissionScanJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func StartCopyMissionScanJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ package copy_mission
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UpdateCopyMissionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func UpdateCopyMissionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ package copy_mission
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/copy_mission"
|
"haixun-backend/internal/logic/copy_mission"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UpsertCopyMissionScanScheduleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func UpsertCopyMissionScanScheduleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
package threads_account
|
package crm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
"haixun-backend/internal/logic/crm"
|
||||||
"haixun-backend/internal/logic/threads_account"
|
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ListMentionInboxHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func CreateCrmContactHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var req types.ListMentionInboxHandlerReq
|
var req types.CreateCrmContactReq
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
return
|
return
|
||||||
|
|
@ -21,8 +22,8 @@ func ListMentionInboxHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l := threads_account.NewListMentionInboxLogic(r.Context(), svcCtx)
|
l := crm.NewCreateCrmContactLogic(r.Context(), svcCtx)
|
||||||
data, err := l.ListMentionInbox(&req)
|
data, err := l.CreateCrmContact(&req)
|
||||||
response.Write(r.Context(), w, data, err)
|
response.Write(r.Context(), w, data, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package crm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"haixun-backend/internal/logic/crm"
|
||||||
|
"haixun-backend/internal/response"
|
||||||
|
"haixun-backend/internal/svc"
|
||||||
|
"haixun-backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeleteCrmContactHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.CrmContactIDPath
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l := crm.NewDeleteCrmContactLogic(r.Context(), svcCtx)
|
||||||
|
err := l.DeleteCrmContact(&req)
|
||||||
|
response.Write(r.Context(), w, nil, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package threads_account
|
package crm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"haixun-backend/internal/logic/threads_account"
|
"haixun-backend/internal/logic/crm"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
@ -11,16 +11,15 @@ import (
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartThreadsOauthHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func GetCrmContactHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var req types.StartThreadsOAuthQuery
|
var req types.CrmContactIDPath
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
l := crm.NewGetCrmContactLogic(r.Context(), svcCtx)
|
||||||
l := threads_account.NewStartThreadsOAuthLogic(r.Context(), svcCtx)
|
data, err := l.GetCrmContact(&req)
|
||||||
data, err := l.StartThreadsOAuth(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
response.Write(r.Context(), w, data, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package threads_account
|
package crm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"haixun-backend/internal/logic/threads_account"
|
"haixun-backend/internal/logic/crm"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
@ -11,15 +11,15 @@ import (
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunThreadsApiSmokeTestHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func ListCrmContactsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var req types.ThreadsAccountPath
|
var req types.ListCrmContactsReq
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l := threads_account.NewRunThreadsAPISmokeTestLogic(r.Context(), svcCtx)
|
l := crm.NewListCrmContactsLogic(r.Context(), svcCtx)
|
||||||
data, err := l.RunThreadsAPISmokeTest(&req)
|
data, err := l.ListCrmContacts(&req)
|
||||||
response.Write(r.Context(), w, data, err)
|
response.Write(r.Context(), w, data, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package threads_account
|
package crm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"haixun-backend/internal/logic/threads_account"
|
"haixun-backend/internal/logic/crm"
|
||||||
"haixun-backend/internal/response"
|
"haixun-backend/internal/response"
|
||||||
"haixun-backend/internal/svc"
|
"haixun-backend/internal/svc"
|
||||||
"haixun-backend/internal/types"
|
"haixun-backend/internal/types"
|
||||||
|
|
@ -11,15 +11,15 @@ import (
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunThreadsApiPlaygroundHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func UpdateCrmContactHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var req types.RunThreadsAPIPlaygroundHandlerReq
|
var req types.UpdateCrmContactReq
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l := threads_account.NewRunThreadsAPIPlaygroundLogic(r.Context(), svcCtx)
|
l := crm.NewUpdateCrmContactLogic(r.Context(), svcCtx)
|
||||||
data, err := l.RunThreadsAPIPlayground(&req)
|
data, err := l.UpdateCrmContact(&req)
|
||||||
response.Write(r.Context(), w, data, err)
|
response.Write(r.Context(), w, data, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package member
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"haixun-backend/internal/logic/member"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetMemberCapabilitiesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
l := member.NewGetMemberCapabilitiesLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.GetMemberCapabilities()
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateContentPlanHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.CreateContentPlanHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewCreateContentPlanLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.CreateContentPlan(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateFeedbackEventHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.CreateFeedbackEventHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewCreateFeedbackEventLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.CreateFeedbackEvent(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateFormulaPoolHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.CreateFormulaPoolHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewCreateFormulaPoolLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.CreateFormulaPool(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateKnowledgeSourceHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.CreateKnowledgeSourceHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewCreateKnowledgeSourceLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.CreateKnowledgeSource(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DeletePersonaCopyDraftHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.CopyDraftPath
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewDeletePersonaCopyDraftLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.DeletePersonaCopyDraft(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DeleteStylePresetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.StylePresetPath
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewDeleteStylePresetLogic(r.Context(), svcCtx)
|
|
||||||
err := l.DeleteStylePreset(&req)
|
|
||||||
response.Write(r.Context(), w, nil, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GenerateFromContentFormulaHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.GenerateFromContentFormulaHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l := persona.NewGenerateFromContentFormulaLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.GenerateFromContentFormula(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GeneratePersonaTopicMatrixHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.GeneratePersonaTopicMatrixHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l := persona.NewGeneratePersonaTopicMatrixLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.GeneratePersonaTopicMatrix(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ListContentPlansHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.PersonaPath
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewListContentPlansLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.ListContentPlans(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ListFormulaPoolsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.PersonaPath
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewListFormulaPoolsLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.ListFormulaPools(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ListKnowledgeChunksHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.PersonaPath
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewListKnowledgeChunksLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.ListKnowledgeChunks(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ListKnowledgeSourcesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.PersonaPath
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewListKnowledgeSourcesLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.ListKnowledgeSources(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ListPersonaContentInboxHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.ListPersonaContentInboxHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l := persona.NewListPersonaContentInboxLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.ListPersonaContentInbox(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ListStylePresetsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.PersonaPath
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewListStylePresetsLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.ListStylePresets(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ListTopicCandidatesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.PersonaPath
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewListTopicCandidatesLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.ListTopicCandidates(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PrunePersonaCopyDraftsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.PrunePersonaCopyDraftsHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewPrunePersonaCopyDraftsLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.PrunePersonaCopyDrafts(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SchedulePersonaCopyDraftHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.SchedulePersonaCopyDraftHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l := persona.NewSchedulePersonaCopyDraftLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.SchedulePersonaCopyDraft(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SchedulePersonaDraftsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.SchedulePersonaDraftsHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewSchedulePersonaDraftsLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.SchedulePersonaDrafts(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
)
|
|
||||||
|
|
||||||
func StartPersonaFormulaDraftJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.StartPersonaFormulaDraftJobHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewStartPersonaFormulaDraftJobLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.StartPersonaFormulaDraftJob(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func StartPersonaRewriteDraftJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.StartPersonaRewriteDraftJobHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewStartPersonaRewriteDraftJobLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.StartPersonaRewriteDraftJob(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
)
|
|
||||||
|
|
||||||
func StartPersonaStyleAnalysisFromTextHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.StartPersonaStyleAnalysisFromTextHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewStartPersonaStyleAnalysisFromTextLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.StartPersonaStyleAnalysisFromText(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
)
|
|
||||||
|
|
||||||
func StartPersonaTopicMatrixJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.StartPersonaTopicMatrixJobHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewStartPersonaTopicMatrixJobLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.StartPersonaTopicMatrixJob(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func UpdateContentPlanHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.UpdateContentPlanHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewUpdateContentPlanLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.UpdateContentPlan(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Code scaffolded by goctl. Safe to edit.
|
|
||||||
// goctl 1.10.1
|
|
||||||
|
|
||||||
package persona
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"haixun-backend/internal/logic/persona"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func UpsertStylePresetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.UpsertStylePresetHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := persona.NewUpsertStylePresetLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.UpsertStylePreset(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package placement_topic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"haixun-backend/internal/logic/placement_topic"
|
|
||||||
"haixun-backend/internal/response"
|
|
||||||
"haixun-backend/internal/svc"
|
|
||||||
"haixun-backend/internal/types"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
)
|
|
||||||
|
|
||||||
func StartPlacementTopicOutreachDraftJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var req types.StartPlacementTopicOutreachDraftJobHandlerReq
|
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := svcCtx.Validator.ValidateAll(&req); err != nil {
|
|
||||||
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l := placement_topic.NewStartPlacementTopicOutreachDraftJobLogic(r.Context(), svcCtx)
|
|
||||||
data, err := l.StartPlacementTopicOutreachDraftJob(&req)
|
|
||||||
response.Write(r.Context(), w, data, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue