fix frontend

This commit is contained in:
王性驊 2026-07-13 01:15:30 +00:00
parent fcc76911bb
commit e41c1f96a2
407 changed files with 36971 additions and 4577 deletions

View File

@ -1,6 +1,6 @@
# 只保留會重複打的指令;其餘直接 go / migrate。 # 只保留會重複打的指令;其餘直接 go / migrate。
.PHONY: build test gen-api tidy migrate-up migrate-down .PHONY: build test test-integration gen-api tidy migrate-up migrate-down
build: build:
go build -o bin/gateway . go build -o bin/gateway .
@ -9,6 +9,16 @@ build:
test: test:
go test ./... go test ./...
# M1M5 integration gates
test-integration:
go test ./internal/module/member/usecase/ ./internal/middleware/ \
./internal/module/threads/... ./internal/module/job/... ./internal/module/appnotif/... \
./internal/module/usage/... ./internal/module/ai/... ./internal/module/search/... \
./internal/module/permission/... ./internal/module/studio/... \
./internal/module/inspire/... ./internal/module/scout/... \
./internal/logic/extension/ ./internal/logic/media/ \
-count=1 -timeout 180s
# 改 generate/api/*.api 後執行handler 勿手寫 # 改 generate/api/*.api 後執行handler 勿手寫
gen-api: gen-api:
goctl api go -api generate/api/gateway.api -dir . -style go_zero --home generate/goctl goctl api go -api generate/api/gateway.api -dir . -style go_zero --home generate/goctl

View File

@ -1,21 +1,49 @@
package main package main
import ( import (
"context"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"time" "time"
"apps/backend/internal/config" "apps/backend/internal/config"
"apps/backend/internal/module/ai"
appnotifRepo "apps/backend/internal/module/appnotif/repository"
appnotifUC "apps/backend/internal/module/appnotif/usecase"
fsDomain "apps/backend/internal/module/filestorage/domain"
"apps/backend/internal/module/filestorage/noop"
"apps/backend/internal/module/filestorage/s3store"
jobDomain "apps/backend/internal/module/job/domain"
jobRepo "apps/backend/internal/module/job/repository"
jobUC "apps/backend/internal/module/job/usecase"
memberDomain "apps/backend/internal/module/member/domain"
memberRepo "apps/backend/internal/module/member/repository"
scoutRepo "apps/backend/internal/module/scout/repository"
scoutUC "apps/backend/internal/module/scout/usecase"
studioPublish "apps/backend/internal/module/studio/publish"
studioRepo "apps/backend/internal/module/studio/repository"
studioUC "apps/backend/internal/module/studio/usecase"
threadsDomain "apps/backend/internal/module/threads/domain"
threadsProv "apps/backend/internal/module/threads/provider"
threadsRepo "apps/backend/internal/module/threads/repository"
threadsUC "apps/backend/internal/module/threads/usecase"
usageDomain "apps/backend/internal/module/usage/domain"
usageRepo "apps/backend/internal/module/usage/repository"
usageUC "apps/backend/internal/module/usage/usecase"
"github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
// Independent worker binary (A-03). M0 stub: config load + heartbeat log only. // Workerdemo + threads_token_renew + persona_analyze_* + outbox publish
// Outbox/job runners land in M2/M4 (T133/T169). //
// cd apps/backend && go build -o bin/worker ./cmd/worker
// ./bin/worker -f etc/gateway.yaml
func main() { func main() {
configFile := flag.String("f", "etc/gateway.yaml", "config file") configFile := flag.String("f", "etc/gateway.yaml", "config file")
flag.Parse() flag.Parse()
@ -25,31 +53,319 @@ func main() {
c.ApplyEnv() c.ApplyEnv()
workerID := c.Worker.ID workerID := c.Worker.ID
if workerID == "" { if workerID == "" || workerID == "worker-1" || workerID == "worker-local-1" {
workerID = "worker-1" workerID = "demo-worker-1"
} }
interval := time.Duration(c.Worker.PollIntervalMs) * time.Millisecond interval := time.Duration(c.Worker.PollIntervalMs) * time.Millisecond
if interval <= 0 { if interval <= 0 {
interval = 2 * time.Second interval = 2 * time.Second
} }
logx.Infof("haixun worker stub started id=%s interval=%s (no publish yet)", workerID, interval) appN := appnotifUC.New(appnotifRepo.NewMonStore(c.Mongo.URI, c.Mongo.Database))
fmt.Printf("worker stub running id=%s (Ctrl+C to stop)\n", workerID) jobs := jobUC.New(jobRepo.NewMonStore(c.Mongo.URI, c.Mongo.Database))
jobs.Notifier = appN
secret := c.Auth.AccessSecret
if secret == "" {
secret = "dev-token-secret"
}
var provider threadsDomain.Provider
if strings.TrimSpace(c.Platform.ThreadsAppId) != "" && strings.TrimSpace(c.Platform.ThreadsAppSecret) != "" {
provider = threadsProv.NewMeta(c.Platform.ThreadsAppId, c.Platform.ThreadsAppSecret)
logx.Info("worker threads: meta provider")
} else {
provider = &threadsProv.FakeProvider{}
logx.Info("worker threads: fake provider")
}
threadsSvc := threadsUC.New(threadsRepo.NewMonStore(c.Mongo.URI, c.Mongo.Database), provider, secret)
threadsSvc.Renew = jobs
// usage + AI keys與 gateway 對齊persona LLM 需真 key
members := memberRepo.NewMoncStore(c.Mongo.URI, c.Mongo.Database, c.CacheRedis)
keyRes := &usageUC.SettingsResolver{
Members: members,
PlatformAI: c.Platform.AIKey,
PlatformXAI: c.Platform.XAIKey,
PlatformOpenCode: c.Platform.OpenCodeKey,
PlatformExa: c.Platform.ExaKey,
}
if keyRes.PlatformAI == "" && keyRes.PlatformXAI == "" && keyRes.PlatformOpenCode == "" {
keyRes.PlatformAI = "fake-platform-ai"
logx.Info("worker usage: no platform AI keys — fake-platform-ai for quota only")
}
usageSvc := usageUC.New(usageRepo.NewMonStore(c.Mongo.URI, c.Mongo.Database), keyRes)
aiRegistry := ai.NewRegistry()
// Outboxscout 發文:有 Meta 憑證則真 Threads Graph否則 Fake單元測試用
var pub studioPublish.Transport
if strings.TrimSpace(c.Platform.ThreadsAppId) != "" && strings.TrimSpace(c.Platform.ThreadsAppSecret) != "" {
pub = studioPublish.NewMeta("https://graph.threads.net")
logx.Info("worker studio: meta publish transport (real Threads)")
} else {
pub = studioPublish.NewFake()
logx.Info("worker studio: fake publish transport (no Threads app credentials)")
}
studio := studioUC.New(studioRepo.NewMonStore(c.Mongo.URI, c.Mongo.Database), pub)
studio.Accounts = threadsSvc
studio.Usage = usageSvc
studio.AI = &ai.FakeClient{}
studio.AIRegistry = aiRegistry
studio.Keys = &workerAIKeys{Members: members, Resolver: keyRes}
// 發文暫存圖Meta 發成功後刪 temp/*
store := workerStorage(c)
studio.Storage = store
studio.StoragePublicBase = strings.TrimSpace(c.ObjectStorage.PublicBaseURL)
scoutSvc := scoutUC.New(scoutRepo.NewMonStore(c.Mongo.URI, c.Mongo.Database))
scoutSvc.Settings = &workerDevMode{Members: members}
scoutSvc.Transport = pub
scoutSvc.Accounts = threadsSvc
studio.Crawler = scoutSvc
// worker 執行 job 本體,不經 API 再入列
studio.Jobs = nil
logx.Infof("haixun worker started id=%s interval=%s (demo + token_renew + persona_analyze + compose_mimic + outbox)", workerID, interval)
fmt.Printf("worker running id=%s interval=%s (Ctrl+C to stop)\n", workerID, interval)
sig := make(chan os.Signal, 1) sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
tick := time.NewTicker(interval) tick := time.NewTicker(interval)
defer tick.Stop() defer tick.Stop()
ctx := context.Background()
for { for {
select { select {
case <-sig: case <-sig:
logx.Infof("worker %s shutting down", workerID) logx.Infof("worker %s shutting down", workerID)
return return
case t := <-tick.C: case <-tick.C:
// Health heartbeat only — no job/outbox processing in M0. // 1) claim one due job
logx.Infof("worker %s heartbeat at %s", workerID, t.UTC().Format(time.RFC3339)) j, err := jobs.ClaimNext(ctx, workerID)
if err == nil {
switch j.TemplateType {
case "", jobDomain.TemplateDemo:
logx.Infof("worker %s demo job %s", workerID, j.ID)
if _, err := jobs.RunDemoToSuccess(ctx, j.ID); err != nil {
logx.Errorf("worker %s job %s failed: %v", workerID, j.ID, err)
_, _ = jobs.FailJob(ctx, j.ID, err.Error())
} else {
logx.Infof("worker %s job %s succeeded", workerID, j.ID)
}
case jobDomain.TemplateThreadsTokenRenew:
logx.Infof("worker %s token renew job %s account=%s", workerID, j.ID, j.RefID)
if err := runTokenRenew(ctx, jobs, threadsSvc, j); err != nil {
logx.Errorf("worker %s renew %s failed: %v", workerID, j.ID, err)
_, _ = jobs.FailJob(ctx, j.ID, err.Error())
} else {
logx.Infof("worker %s renew %s ok", workerID, j.ID)
}
case jobDomain.TemplatePersonaAnalyzeAccount, jobDomain.TemplatePersonaAnalyzeText:
logx.Infof("worker %s persona analyze job %s type=%s persona=%s", workerID, j.ID, j.TemplateType, j.RefID)
if err := runPersonaAnalyze(ctx, jobs, studio, j); err != nil {
logx.Errorf("worker %s persona %s failed: %v", workerID, j.ID, err)
_, _ = jobs.FailJob(ctx, j.ID, err.Error())
} else {
logx.Infof("worker %s persona %s ok", workerID, j.ID)
}
case jobDomain.TemplateComposeMimic:
logx.Infof("worker %s compose_mimic job %s", workerID, j.ID)
if err := runComposeMimic(ctx, jobs, studio, j); err != nil {
logx.Errorf("worker %s compose_mimic %s failed: %v", workerID, j.ID, err)
_, _ = jobs.FailJob(ctx, j.ID, err.Error())
} else {
logx.Infof("worker %s compose_mimic %s ok", workerID, j.ID)
}
case jobDomain.TemplatePlayGenerateScript:
logx.Infof("worker %s play_generate_script job %s play=%s", workerID, j.ID, j.RefID)
if err := runPlayGenerateScript(ctx, jobs, studio, j); err != nil {
logx.Errorf("worker %s play_generate_script %s failed: %v", workerID, j.ID, err)
_, _ = jobs.FailJob(ctx, j.ID, err.Error())
} else {
logx.Infof("worker %s play_generate_script %s ok", workerID, j.ID)
}
default:
logx.Infof("worker %s unknown template %s job %s — fail", workerID, j.TemplateType, j.ID)
_, _ = jobs.FailJob(ctx, j.ID, "unknown template: "+j.TemplateType)
}
}
// 2) outbox due steps
n, err := studio.ProcessDueSteps(ctx, 0)
if err != nil {
logx.Errorf("worker %s outbox tick: %v", workerID, err)
} else if n > 0 {
logx.Infof("worker %s outbox published %d step(s)", workerID, n)
}
// 3) 終態任務超過 2 天自動清除
if purged, err := jobs.PurgeExpiredTerminal(ctx); err != nil {
logx.Errorf("worker %s purge jobs: %v", workerID, err)
} else if purged > 0 {
logx.Infof("worker %s purged %d expired terminal job(s)", workerID, purged)
} }
} }
} }
}
func runTokenRenew(ctx context.Context, jobs *jobUC.Service, threads *threadsUC.Service, j *jobDomain.Job) error {
if j.RefID == "" {
return fmt.Errorf("缺少帳號 ref_id")
}
_, _ = jobs.MarkRunningProgress(ctx, j.ID, 15, "定期延長 Threads token · 準備中")
_, _ = jobs.MarkRunningProgress(ctx, j.ID, 40, "定期延長 Threads token · 向平台刷新中")
acc, err := threads.Refresh(ctx, j.OwnerUID, j.RefID)
if err != nil {
return err
}
_, _ = jobs.MarkRunningProgress(ctx, j.ID, 75, "定期延長 Threads token · 排程下一次")
// 下次再約 30 天
next := jobDomain.NowNano() + jobDomain.DefaultTokenRenewDelayNs
if acc != nil && acc.SessionExpiresAt > 0 {
early := acc.SessionExpiresAt - int64(30*24*time.Hour)
if early > jobDomain.NowNano() {
next = early
}
}
_ = jobs.ScheduleTokenRenew(ctx, j.OwnerUID, j.RefID, next)
_, _ = jobs.MarkRunningProgress(ctx, j.ID, 90, "定期延長 Threads token · 寫入完成")
_, err = jobs.SucceedJob(ctx, j.ID, "定期延長完成 · 已排程約 30 天後再執行")
return err
}
func runPersonaAnalyze(ctx context.Context, jobs *jobUC.Service, studio *studioUC.Service, j *jobDomain.Job) error {
if j.RefID == "" {
return fmt.Errorf("缺少人設 ref_id")
}
startSum := "人設分析 · 文字分析中…"
if j.TemplateType == jobDomain.TemplatePersonaAnalyzeAccount {
startSum = "人設分析 · 爬取公開貼文 + AI 分析中…(可離開頁面)"
}
_, _ = jobs.MarkRunningProgress(ctx, j.ID, 10, startSum)
err := studio.ExecutePersonaAnalyzeJob(ctx, j.TemplateType, j.OwnerUID, j.RefID, j.Payload, func(pct int, sum string) {
if pct < 10 {
pct = 10
}
if pct > 95 {
pct = 95
}
_, _ = jobs.MarkRunningProgress(ctx, j.ID, pct, sum)
})
if err != nil {
return err
}
_, err = jobs.SucceedJob(ctx, j.ID, "人設分析完成 · 已寫入指紋/範本")
return err
}
func runPlayGenerateScript(ctx context.Context, jobs *jobUC.Service, studio *studioUC.Service, j *jobDomain.Job) error {
var pl jobUC.PlayGenerateScriptPayload
if err := json.Unmarshal([]byte(j.Payload), &pl); err != nil {
return fmt.Errorf("invalid play_generate_script payload: %w", err)
}
playID := strings.TrimSpace(pl.PlayID)
if playID == "" {
playID = strings.TrimSpace(j.RefID)
}
if playID == "" {
return fmt.Errorf("empty play_id")
}
_, _ = jobs.MarkRunningProgress(ctx, j.ID, 15, "劇本產文 · 準備 AI一次產全部…")
llmCtx, cancel := context.WithTimeout(ctx, 4*time.Minute)
defer cancel()
_, _ = jobs.MarkRunningProgress(ctx, j.ID, 40, "劇本產文 · 呼叫模型中(可離開頁面)…")
n, err := studio.GeneratePlayScript(llmCtx, j.OwnerUID, playID, pl.OnlyEmpty)
if err != nil {
return err
}
pl.Filled = n
body, _ := json.Marshal(pl)
_, _ = jobs.MarkRunningProgress(ctx, j.ID, 90, fmt.Sprintf("劇本產文 · 已寫入 %d 步…", n))
_, err = jobs.SucceedJobWithPayload(ctx, j.ID, fmt.Sprintf("劇本產文完成 · 已填 %d 步,請回互回方案查看", n), string(body))
return err
}
func runComposeMimic(ctx context.Context, jobs *jobUC.Service, studio *studioUC.Service, j *jobDomain.Job) error {
var pl jobUC.ComposeMimicPayload
if err := json.Unmarshal([]byte(j.Payload), &pl); err != nil {
return fmt.Errorf("invalid compose_mimic payload: %w", err)
}
if strings.TrimSpace(pl.SourceText) == "" {
return fmt.Errorf("empty source_text")
}
_, _ = jobs.MarkRunningProgress(ctx, j.ID, 15, "仿寫貼文 · 準備 AI…")
// worker 不綁 gateway 120s給模型較長時間reasoning 模型需要)
llmCtx, cancel := context.WithTimeout(ctx, 4*time.Minute)
defer cancel()
_, _ = jobs.MarkRunningProgress(ctx, j.ID, 35, "仿寫貼文 · 呼叫模型中(可離開頁面)…")
text, err := studio.Mimic(llmCtx, j.OwnerUID, pl.SourceText, pl.PersonaID, pl.StructureNotes)
if err != nil {
return err
}
_, _ = jobs.MarkRunningProgress(ctx, j.ID, 90, "仿寫貼文 · 寫入結果…")
pl.ResultText = text
body, _ := json.Marshal(pl)
_, err = jobs.SucceedJobWithPayload(ctx, j.ID, "仿寫完成 · 已可套用到正文", string(body))
return err
}
// workerAIKeys — 與 gateway studioAIKeys 對齊
type workerAIKeys struct {
Members memberDomain.Repository
Resolver *usageUC.SettingsResolver
}
func (k *workerAIKeys) ResolveAI(ctx context.Context, ownerUID int64) (provider, model, apiKey string, err error) {
st, err := k.Members.GetSettings(ctx, ownerUID)
if err != nil || st == nil {
st = memberDomain.DefaultSettings(ownerUID)
}
// 完全依會員 AI 設定provider / model / key不寫死模型
provider = ai.NormalizeProvider(st.Provider)
model = strings.TrimSpace(st.Model)
if model == "" {
return provider, "", "", fmt.Errorf("請到設定選擇 AI 模型")
}
if k.Resolver != nil {
_, key, rerr := k.Resolver.ResolveKey(ctx, ownerUID, usageDomain.MeterAICopy)
if rerr == nil && strings.TrimSpace(key) != "" {
return provider, model, key, nil
}
}
if key := st.KeyForProvider(provider); key != "" {
return provider, model, key, nil
}
return provider, model, "", fmt.Errorf("no AI key請到設定填寫 Key或確認平台已配置")
}
type workerDevMode struct {
Members memberDomain.Repository
}
func (d *workerDevMode) DevModeEnabled(ctx context.Context, uid int64) (bool, error) {
if d == nil || d.Members == nil {
return false, nil
}
st, err := d.Members.GetSettings(ctx, uid)
if err != nil || st == nil {
return false, nil
}
return st.DevModeEnabled, nil
}
func workerStorage(c config.Config) fsDomain.Storage {
os := c.ObjectStorage
if strings.TrimSpace(os.Endpoint) == "" {
logx.Info("worker storage: disabled (no endpoint)")
return noop.New()
}
s, err := s3store.New(s3store.Config{
Endpoint: os.Endpoint, Region: os.Region, Bucket: os.Bucket,
AccessKey: os.AccessKey, SecretKey: os.SecretKey,
UsePathStyle: os.UsePathStyle, PublicBaseURL: os.PublicBaseURL,
})
if err != nil {
logx.Errorf("worker storage init failed: %v", err)
return noop.New()
}
logx.Infof("worker storage: minio/s3 bucket=%s (ephemeral publish images cleanup enabled)", os.Bucket)
return s
}

View File

@ -3,7 +3,8 @@
Name: haixun-gateway Name: haixun-gateway
Host: 0.0.0.0 Host: 0.0.0.0
Port: 8888 Port: 8888
Timeout: 30000 # AI 產文/試產可能超過 30s過短會 503 且前端拿不到結果
Timeout: 120000
MaxBytes: 104857600 MaxBytes: 104857600
Log: Log:
@ -32,14 +33,23 @@ Auth:
RefreshSecret: "change-me-refresh-secret" RefreshSecret: "change-me-refresh-secret"
RefreshExpire: 604800 RefreshExpire: 604800
# 平台預設 key所有會員可用會員在設定頁填的 BYOK 優先)
# 也可用環境變數覆寫PLATFORM_XAI_KEY / PLATFORM_OPENCODE_KEY / PLATFORM_EXA_KEY / PLATFORM_AI_KEY
Platform: Platform:
# xAIapi.x.ai— 人設 LLM、文案等
XAIKey: "xai-TtmPWVAz1CdssptRetj9iOFSdSG2LZDffEOTKzIiDmVSDqUL62lVdDladlQIXAwoeHtowWraVqYuHzl1"
# 相容舊名XAIKey 空時會用 AIKey
AIKey: "" AIKey: ""
ExaKey: "" # OpenCode Goopencode.ai/zen/go
ThreadsAppId: "" OpenCodeKey: "sk-sJkJVG2Q3tQEGqgl4cCy10QCVnrHpvuzR8RuE2eTuvruXiVNromsQ7zo6db0Ssj1"
ThreadsAppSecret: "" # Exa 網搜
ExaKey: "5d9af9b2-a847-4295-a110-f666049e1073"
ThreadsAppId: "2733369680379930"
ThreadsAppSecret: "31db4afdd8e348d75d76dee8fb9a42d3"
Worker: Worker:
ID: "worker-local-1" # demo-worker 預設 idcmd/worker 若見 worker-local-1 會改成 demo-worker-1
ID: "demo-worker-1"
PollIntervalMs: 2000 PollIntervalMs: 2000
Bcrypt: Bcrypt:
@ -62,7 +72,7 @@ OAuth:
# 正式 Mailgun SMTP 例: # 正式 Mailgun SMTP 例:
# Host: smtp.mailgun.org Port: 587 # Host: smtp.mailgun.org Port: 587
# User: postmaster@YOUR_DOMAIN Password: <API SMTP password> # User: postmaster@YOUR_DOMAIN Password: <API SMTP password>
# DevExposeCode: false # DevExposeCode: false試產
# #
# env 覆寫MAIL_SMTP_* 或 HAIXUN_SMTP_*(舊名相容) # env 覆寫MAIL_SMTP_* 或 HAIXUN_SMTP_*(舊名相容)
Mail: Mail:
@ -77,7 +87,7 @@ Mail:
# 前端 origin重設信連結 + 預設 logo URL/brand-mark.jpg # 前端 origin重設信連結 + 預設 logo URL/brand-mark.jpg
# 遠端 devhttps://threads-tool-dev.30cm.net # 遠端 devhttps://threads-tool-dev.30cm.net
PublicWebBase: "http://127.0.0.1:5173" PublicWebBase: "https://threads-tool-dev.30cm.net"
# 郵件品牌hermes 模板LogoURL 空 = PublicWebBase/brand-mark.jpg # 郵件品牌hermes 模板LogoURL 空 = PublicWebBase/brand-mark.jpg
Brand: Brand:

View File

@ -39,6 +39,8 @@ func main() {
ctx := svc.NewServiceContext(c) ctx := svc.NewServiceContext(c)
handler.RegisterHandlers(server, ctx) handler.RegisterHandlers(server, ctx)
// 靈感 SSE streamgoctl 不生成 streaming handler
handler.RegisterInspireStream(server, ctx)
logx.Infof("haixun gateway %s:%d pid=%d", c.Host, c.Port, os.Getpid()) logx.Infof("haixun gateway %s:%d pid=%d", c.Host, c.Port, os.Getpid())
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)

View File

@ -0,0 +1,13 @@
syntax = "v1"
// ST-16: extension zip download (authenticated member)
@server (
jwt: Auth
group: extension
prefix: /api/v1/extension
middleware: AuthJWT
)
service gateway {
@handler DownloadExtensionZip
get /download returns (Empty)
}

View File

@ -13,3 +13,11 @@ import "auth.api"
import "admin.api" import "admin.api"
import "settings.api" import "settings.api"
import "media.api" import "media.api"
import "threads.api"
import "jobs.api"
import "notifications.api"
import "usage.api"
import "proxy.api"
import "extension.api"
import "studio.api"
import "m5.api"

View File

@ -0,0 +1,59 @@
syntax = "v1"
type (
JobPublic {
Id string `json:"id"`
TemplateType string `json:"template_type"`
Status string `json:"status"`
ProgressSummary string `json:"progress_summary"`
ProgressPercent int `json:"progress_percent"`
Error string `json:"error,optional"`
// 業務關聯threads 帳號 id、人設 id
RefId string `json:"ref_id,optional"`
// 任務 payload JSON{"username":"x"}
Payload string `json:"payload,optional"`
// 排程執行時間 unix ns0立刻可領
RunAfter int64 `json:"run_after,optional"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
CompletedAt int64 `json:"completed_at,optional"`
}
JobListData {
List []JobPublic `json:"list"`
}
JobIdPath {
Id string `path:"id"`
}
)
@server (
jwt: Auth
group: jobs
prefix: /api/v1/jobs
middleware: AuthJWT
)
service gateway {
@handler ListJobs
get / returns (JobListData)
@handler GetJob
get /:id (JobIdPath) returns (JobPublic)
// 手動刪除任務(執行中不可刪;終態/已排程可刪)
@handler DeleteJob
delete /:id (JobIdPath) returns (OkData)
}
// 管理員:產生 demo測試任務由 demo-worker 消費)
@server (
jwt: Auth
group: jobs
prefix: /api/v1/jobs
middleware: AuthJWT,AdminAuth
)
service gateway {
@handler StartDemoJob
post /demo returns (JobPublic)
}

View File

@ -0,0 +1,465 @@
syntax = "v1"
// M5: inspire + research + media image + scout
type (
// --- Inspire ---
TrendPublic {
Id string `json:"id"`
Kind string `json:"kind"`
Label string `json:"label"`
Summary string `json:"summary"`
Heat int `json:"heat"`
Keywords []string `json:"keywords"`
Samples []string `json:"samples"`
SourceLabel string `json:"source_label"`
ObservedAt int64 `json:"observed_at"`
}
TrendListData {
List []TrendPublic `json:"list"`
}
InspireElementPublic {
Id string `json:"id"`
Kind string `json:"kind"`
Title string `json:"title"`
Body string `json:"body"`
RefId string `json:"ref_id,optional"`
Reusable bool `json:"reusable"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
InspireElementListData {
List []InspireElementPublic `json:"list"`
}
InspireElementSaveReq {
Id string `json:"id,optional"`
Kind string `json:"kind,optional"`
Title string `json:"title"`
Body string `json:"body"`
RefId string `json:"ref_id,optional"`
Reusable bool `json:"reusable,optional"`
}
InspireElementIdPath {
Id string `path:"id"`
}
InspireDraftPublic {
Title string `json:"title,optional"`
Body string `json:"body"`
}
InspireMessagePublic {
Id string `json:"id"`
Role string `json:"role"`
Text string `json:"text"`
Draft InspireDraftPublic `json:"draft,optional"`
CreatedAt int64 `json:"created_at"`
}
InspireSessionPublic {
Id string `json:"id"`
Title string `json:"title,optional"`
Messages []InspireMessagePublic `json:"messages"`
PinnedElementIds []string `json:"pinned_element_ids"`
CreatedAt int64 `json:"created_at,optional"`
UpdatedAt int64 `json:"updated_at"`
}
// 列表摘要(不含完整 messages
InspireSessionSummaryPublic {
Id string `json:"id"`
Title string `json:"title"`
Preview string `json:"preview"`
MessageCount int `json:"message_count"`
UpdatedAt int64 `json:"updated_at"`
CreatedAt int64 `json:"created_at"`
Active bool `json:"active"`
}
InspireSessionListData {
List []InspireSessionSummaryPublic `json:"list"`
}
InspireSessionIdPath {
Id string `path:"id"`
}
InspireSessionCreateReq {
Title string `json:"title,optional"`
}
InspireChatReq {
Message string `json:"message"`
PinnedIds []string `json:"pinned_ids,optional"`
Mode string `json:"mode,optional"` // chat|generate
PersonaId string `json:"persona_id,optional"`
SessionId string `json:"session_id,optional"` // 空 = active
// generate 專用:待改寫素材(鎖定內容);空則拒絕產文
Material string `json:"material,optional"`
}
InspireChatData {
Session InspireSessionPublic `json:"session"`
Messages []InspireMessagePublic `json:"messages"`
}
// 觀測:與送出相同的 prompt 組裝(不呼叫 AI、不計費
InspirePromptPreviewReq {
Message string `json:"message,optional"`
PinnedIds []string `json:"pinned_ids,optional"`
Mode string `json:"mode,optional"` // chat|generate
PersonaId string `json:"persona_id,optional"`
SessionId string `json:"session_id,optional"`
Material string `json:"material,optional"`
}
InspirePromptPinnedPublic {
Id string `json:"id"`
Kind string `json:"kind"`
Title string `json:"title"`
Body string `json:"body"`
}
InspirePromptBlockPublic {
Title string `json:"title"`
Body string `json:"body"`
}
InspirePromptPreviewData {
Prompt string `json:"prompt"`
Mode string `json:"mode"`
Sections []string `json:"sections"`
Blocks []InspirePromptBlockPublic `json:"blocks"`
Fingerprint string `json:"fingerprint"`
CharCount int `json:"char_count"`
RuneCount int `json:"rune_count"`
Pinned []InspirePromptPinnedPublic `json:"pinned"`
Note string `json:"note,optional"`
}
ResearchSearchReq {
Query string `json:"query"`
}
ResearchHitPublic {
Id string `json:"id"`
Title string `json:"title"`
Snippet string `json:"snippet"`
Url string `json:"url"`
Summary string `json:"summary,optional"`
LearnPoints []string `json:"learn_points,optional"`
ReplyHooks []string `json:"reply_hooks,optional"`
SourceLabel string `json:"source_label,optional"`
}
ResearchSearchData {
List []ResearchHitPublic `json:"list"`
}
GenerateImageReq {
Prompt string `json:"prompt"`
}
GenerateImageData {
Id string `json:"id"`
Url string `json:"url"`
Prompt string `json:"prompt"`
}
// --- Scout ---
BrandPublic {
Id string `json:"id"`
DisplayName string `json:"display_name"`
Brief string `json:"brief"`
TargetAudience string `json:"target_audience,optional"`
Goals string `json:"goals,optional"`
}
BrandListData {
List []BrandPublic `json:"list"`
}
BrandSaveReq {
Id string `json:"id,optional"`
DisplayName string `json:"display_name"`
Brief string `json:"brief,optional"`
TargetAudience string `json:"target_audience,optional"`
Goals string `json:"goals,optional"`
}
BrandIdPath {
Id string `path:"id"`
}
BrandActiveData {
Id string `json:"id"`
}
BrandSetActiveReq {
Id string `json:"id"`
}
ProductPublic {
Id string `json:"id"`
BrandId string `json:"brand_id"`
Label string `json:"label"`
ProductContext string `json:"product_context"`
MatchTags []string `json:"match_tags"`
PainPoints []string `json:"pain_points"`
PlacementUrl string `json:"placement_url,optional"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
ProductListData {
List []ProductPublic `json:"list"`
}
ProductSaveReq {
Id string `json:"id,optional"`
BrandId string `json:"brand_id"`
Label string `json:"label"`
ProductContext string `json:"product_context,optional"`
MatchTags []string `json:"match_tags,optional"`
PainPoints []string `json:"pain_points,optional"`
PlacementUrl string `json:"placement_url,optional"`
}
ProductIdPath {
Id string `path:"id"`
}
ProductListReq {
BrandId string `form:"brand_id,optional"`
}
ImportProductReq {
Url string `json:"url"`
}
ImportProductData {
Label string `json:"label"`
ProductContext string `json:"product_context"`
PainPoints []string `json:"pain_points"`
MatchTags []string `json:"match_tags"`
PlacementUrl string `json:"placement_url"`
SourceNote string `json:"source_note"`
}
ScoutBriefReq {
Intent string `json:"intent"`
BrandId string `json:"brand_id,optional"`
ProductId string `json:"product_id,optional"`
Purpose string `json:"purpose,optional"`
Deep bool `json:"deep,optional"`
}
ScoutBriefPublic {
Intent string `json:"intent"`
Mode string `json:"mode"`
BrandId string `json:"brand_id,optional"`
ProductId string `json:"product_id,optional"`
ProductLabel string `json:"product_label,optional"`
Pains []string `json:"pains"`
Tags []string `json:"tags"`
Periphery []string `json:"periphery"`
ScanTerms []string `json:"scan_terms"`
PlacementNote string `json:"placement_note,optional"`
ResponseStance string `json:"response_stance,optional"`
ThemeKey string `json:"theme_key,optional"`
ThemeLabel string `json:"theme_label,optional"`
ProductContext string `json:"product_context,optional"`
}
ScoutScanReq {
Brief ScoutBriefPublic `json:"brief"`
}
ScoutPostPublic {
Id string `json:"id"`
BrandId string `json:"brand_id,optional"`
Author string `json:"author"`
Text string `json:"text"`
SearchTag string `json:"search_tag"`
Opportunity string `json:"opportunity"`
OutreachStatus string `json:"outreach_status"`
DraftText string `json:"draft_text,optional"`
Score int `json:"score"`
MatchedProductId string `json:"matched_product_id,optional"`
MatchedProductLabel string `json:"matched_product_label,optional"`
MatchReason string `json:"match_reason,optional"`
ScoutMode string `json:"scout_mode,optional"`
IntentSnippet string `json:"intent_snippet,optional"`
ThemeKey string `json:"theme_key,optional"`
ThemeLabel string `json:"theme_label,optional"`
ScanPath string `json:"scan_path,optional"`
CreatedAt int64 `json:"created_at"`
}
ScoutPostListData {
List []ScoutPostPublic `json:"list"`
}
ScoutPostListReq {
BrandId string `form:"brand_id,optional"`
}
ScoutPostIdPath {
Id string `path:"id"`
}
ScoutDraftPathReq {
Id string `path:"id"`
PersonaId string `json:"persona_id,optional"`
}
ScoutSendPathReq {
Id string `path:"id"`
Text string `json:"text,optional"`
AccountId string `json:"account_id,optional"`
}
ScoutThemePath {
ThemeKey string `path:"themeKey"`
}
ScoutHomeworkPublic {
ThemeKey string `json:"theme_key"`
ThemeLabel string `json:"theme_label"`
Purpose string `json:"purpose"`
Brief ScoutBriefPublic `json:"brief"`
CreatedAt int64 `json:"created_at"`
}
ScoutHomeworkListData {
List []ScoutHomeworkPublic `json:"list"`
}
ScoutHomeworkSaveReq {
ThemeKey string `json:"theme_key"`
ThemeLabel string `json:"theme_label"`
Purpose string `json:"purpose,optional"`
Brief ScoutBriefPublic `json:"brief"`
}
ScoutCrawlerSessionReq {
Token string `json:"token"`
}
)
@server (
jwt: Auth
group: inspire
prefix: /api/v1/inspire
middleware: AuthJWT
)
service gateway {
@handler ListTrends
get /trends returns (TrendListData)
@handler RefreshTrends
post /trends/refresh returns (TrendListData)
@handler ListInspireElements
get /elements returns (InspireElementListData)
@handler SaveInspireElement
post /elements (InspireElementSaveReq) returns (InspireElementPublic)
@handler RemoveInspireElement
delete /elements/:id (InspireElementIdPath) returns (OkData)
@handler GetInspireSession
get /session returns (InspireSessionPublic)
@handler ClearInspireSession
post /session/clear returns (InspireSessionPublic)
@handler ListInspireSessions
get /sessions returns (InspireSessionListData)
@handler CreateInspireSession
post /sessions (InspireSessionCreateReq) returns (InspireSessionPublic)
@handler GetInspireSessionById
get /sessions/:id (InspireSessionIdPath) returns (InspireSessionPublic)
@handler ActivateInspireSession
post /sessions/:id/activate (InspireSessionIdPath) returns (InspireSessionPublic)
@handler DeleteInspireSession
delete /sessions/:id (InspireSessionIdPath) returns (InspireSessionPublic)
@handler InspireChat
post /chat (InspireChatReq) returns (InspireChatData)
@handler InspirePromptPreview
post /prompt-preview (InspirePromptPreviewReq) returns (InspirePromptPreviewData)
}
@server (
jwt: Auth
group: research
prefix: /api/v1/research
middleware: AuthJWT
)
service gateway {
@handler ResearchSearch
post /search (ResearchSearchReq) returns (ResearchSearchData)
}
@server (
jwt: Auth
group: media
prefix: /api/v1/media
middleware: AuthJWT
)
service gateway {
@handler GenerateImage
post /generate-image (GenerateImageReq) returns (GenerateImageData)
}
@server (
jwt: Auth
group: scout
prefix: /api/v1/scout
middleware: AuthJWT
)
service gateway {
@handler ListBrands
get /brands returns (BrandListData)
@handler SaveBrand
post /brands (BrandSaveReq) returns (BrandPublic)
@handler GetBrand
get /brands/:id (BrandIdPath) returns (BrandPublic)
@handler RemoveBrand
delete /brands/:id (BrandIdPath) returns (OkData)
@handler GetActiveBrand
get /brands-active returns (BrandActiveData)
@handler SetActiveBrand
post /brands-active (BrandSetActiveReq) returns (BrandActiveData)
@handler ListProducts
get /products (ProductListReq) returns (ProductListData)
@handler ListAllProducts
get /products/all returns (ProductListData)
@handler SaveProduct
post /products (ProductSaveReq) returns (ProductPublic)
@handler GetProduct
get /products/:id (ProductIdPath) returns (ProductPublic)
@handler RemoveProduct
delete /products/:id (ProductIdPath) returns (OkData)
@handler ImportProduct
post /products/import (ImportProductReq) returns (ImportProductData)
@handler PrepareBrief
post /brief (ScoutBriefReq) returns (ScoutBriefPublic)
@handler RunScan
post /scan (ScoutScanReq) returns (ScoutPostListData)
@handler ListScoutPosts
get /posts (ScoutPostListReq) returns (ScoutPostListData)
@handler DraftOutreach
post /posts/:id/draft (ScoutDraftPathReq) returns (ScoutPostPublic)
@handler SkipOutreach
post /posts/:id/skip (ScoutPostIdPath) returns (ScoutPostPublic)
@handler MarkPublished
post /posts/:id/mark-published (ScoutPostIdPath) returns (ScoutPostPublic)
@handler SendOutreach
post /posts/:id/send (ScoutSendPathReq) returns (ScoutPostPublic)
@handler RemoveScoutPost
delete /posts/:id (ScoutPostIdPath) returns (OkData)
@handler RemoveScoutTheme
delete /themes/:themeKey (ScoutThemePath) returns (OkData)
@handler ListHomework
get /homework returns (ScoutHomeworkListData)
@handler SaveHomework
post /homework (ScoutHomeworkSaveReq) returns (ScoutHomeworkPublic)
@handler GetHomework
get /homework/:themeKey (ScoutThemePath) returns (ScoutHomeworkPublic)
@handler RemoveHomework
delete /homework/:themeKey (ScoutThemePath) returns (OkData)
@handler SetCrawlerSession
post /crawler-session (ScoutCrawlerSessionReq) returns (OkData)
@handler ClearCrawlerSession
delete /crawler-session returns (OkData)
}

View File

@ -0,0 +1,46 @@
syntax = "v1"
type (
NotificationPublic {
Id string `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
Kind string `json:"kind"` // job|system
RefType string `json:"ref_type"`
RefId string `json:"ref_id,optional"`
ReadAt int64 `json:"read_at,optional"`
CreatedAt int64 `json:"created_at"`
}
NotificationListData {
List []NotificationPublic `json:"list"`
}
UnreadCountData {
Count int64 `json:"count"`
}
NotificationIdPath {
Id string `path:"id"`
}
)
@server (
jwt: Auth
group: notifications
prefix: /api/v1/notifications
middleware: AuthJWT
)
service gateway {
@handler ListNotifications
get / returns (NotificationListData)
@handler UnreadNotificationCount
get /unread-count returns (UnreadCountData)
@handler MarkNotificationRead
post /:id/read (NotificationIdPath) returns (OkData)
@handler MarkAllNotificationsRead
post /read-all returns (OkData)
}

View File

@ -0,0 +1,45 @@
syntax = "v1"
type (
AICompleteReq {
Prompt string `json:"prompt"`
Model string `json:"model,optional"`
Meter string `json:"meter,optional"` // default ai_copy
}
AICompleteData {
Text string `json:"text"`
KeyMode string `json:"key_mode"`
Model string `json:"model"`
}
SearchReq {
Query string `json:"query"`
Limit int `json:"limit,optional"`
}
SearchHit {
Title string `json:"title"`
Url string `json:"url"`
Snippet string `json:"snippet"`
}
SearchData {
List []SearchHit `json:"list"`
KeyMode string `json:"key_mode"`
}
)
@server (
jwt: Auth
group: proxy
prefix: /api/v1/proxy
middleware: AuthJWT
)
service gateway {
@handler AIComplete
post /ai/complete (AICompleteReq) returns (AICompleteData)
@handler ExaSearch
post /search (SearchReq) returns (SearchData)
}

View File

@ -1,6 +1,12 @@
syntax = "v1" syntax = "v1"
type ( type (
// GetAi設定 + 模型清單一併回(避免前端分開打兩支)
GetAiReq {
// 可選:預覽另一個 provider 的模型清單(不改存檔)
Provider string `form:"provider,optional"`
}
AiSettingsData { AiSettingsData {
Provider string `json:"provider"` Provider string `json:"provider"`
Model string `json:"model"` Model string `json:"model"`
@ -9,6 +15,15 @@ type (
ApiKeyConfigured bool `json:"api_key_configured"` ApiKeyConfigured bool `json:"api_key_configured"`
ResearchApiKeyConfigured bool `json:"research_api_key_configured"` ResearchApiKeyConfigured bool `json:"research_api_key_configured"`
PlatformKeyAvailable bool `json:"platform_key_available"` PlatformKeyAvailable bool `json:"platform_key_available"`
// Models — 目前 models_provider 的可用模型(真拉 + 5 分鐘快取)
Models []string `json:"models"`
ModelsProvider string `json:"models_provider"`
// SelectedModel — 與 Model 相同;明確給前端「上次設定的模型」
SelectedModel string `json:"selected_model"`
// ModelsFromCache — 這次模型清單是否來自快取
ModelsFromCache bool `json:"models_from_cache,optional"`
// ModelsError — 真拉失敗時提示(仍回 fallback models
ModelsError string `json:"models_error,optional"`
} }
SaveAiReq { SaveAiReq {
@ -45,6 +60,25 @@ type (
ListModelsData { ListModelsData {
List []string `json:"list"` List []string `json:"list"`
Provider string `json:"provider"` Provider string `json:"provider"`
// SelectedModel — 若該 provider 為使用者目前存檔 provider回傳已選 model
SelectedModel string `json:"selected_model,optional"`
FromCache bool `json:"from_cache,optional"`
ModelsError string `json:"models_error,optional"`
}
// Threads OAuth 平台狀態App Secret 永不回傳)
ThreadsSettingsData {
Provider string `json:"provider"` // fake | meta
Configured bool `json:"configured"`
OauthReady bool `json:"oauth_ready"`
ConnectPath string `json:"connect_path"` // 前端連帳入口
// 給 Meta 後台「Valid OAuth Redirect URIs」貼上用
CallbackUrl string `json:"callback_url"`
// 前端公開 origin應 https
PublicWebBase string `json:"public_web_base"`
// App Id 遮罩,例 2733…9930未設定空字串
AppIdMasked string `json:"app_id_masked,optional"`
Hint string `json:"hint"`
} }
) )
@ -55,7 +89,7 @@ type (
) )
service gateway { service gateway {
@handler GetAi @handler GetAi
get /ai returns (AiSettingsData) get /ai (GetAiReq) returns (AiSettingsData)
@handler SaveAi @handler SaveAi
put /ai (SaveAiReq) returns (AiSettingsData) put /ai (SaveAiReq) returns (AiSettingsData)
@ -68,4 +102,7 @@ service gateway {
@handler ListModels @handler ListModels
get /models (ListModelsReq) returns (ListModelsData) get /models (ListModelsReq) returns (ListModelsData)
@handler GetThreads
get /threads returns (ThreadsSettingsData)
} }

View File

@ -0,0 +1,568 @@
syntax = "v1"
// M4 Studio: personas / plays / outbox / compose / own-posts / mentions
type (
PersonaDraftPublic {
Identity string `json:"identity"`
Tone string `json:"tone"`
Audience string `json:"audience"`
Hooks string `json:"hooks"`
LanguageFingerprint string `json:"language_fingerprint"`
Rhythm string `json:"rhythm"`
Punctuation string `json:"punctuation"`
ContentPatterns string `json:"content_patterns"`
KnowledgeTranslation string `json:"knowledge_translation"`
CtaStyle string `json:"cta_style"`
Examples string `json:"examples"`
Avoid string `json:"avoid"`
}
StyleDimensionPublic {
Summary string `json:"summary"`
Evidence []string `json:"evidence,optional"`
}
PersonaStylePublic {
Draft PersonaDraftPublic `json:"draft"`
DraftText string `json:"draft_text"`
Source string `json:"source"`
BenchmarkUsername string `json:"benchmark_username,optional"`
SourceLabel string `json:"source_label,optional"`
SampleCount int `json:"sample_count"`
AnalyzedAt int64 `json:"analyzed_at,optional"`
SamplePreviews []string `json:"sample_previews,optional"`
// 8Dd1Tone…d8Risk → { summary, evidence }
Dimensions map[string]StyleDimensionPublic `json:"dimensions,optional"`
}
PersonaGuardPublic {
Avoid []string `json:"avoid"`
MaxChars int `json:"max_chars"`
BanAiTone bool `json:"ban_ai_tone"`
}
PersonaPublic {
Id string `json:"id"`
Name string `json:"name"`
Brief string `json:"brief"`
Status string `json:"status"`
Style PersonaStylePublic `json:"style"`
Guard PersonaGuardPublic `json:"guard"`
Voice string `json:"voice,optional"`
Notes string `json:"notes,optional"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
PersonaListData {
List []PersonaPublic `json:"list"`
}
PersonaSaveReq {
Id string `json:"id,optional"`
Name string `json:"name"`
Brief string `json:"brief,optional"`
Status string `json:"status,optional"`
Style PersonaStylePublic `json:"style,optional"`
Guard PersonaGuardPublic `json:"guard,optional"`
Voice string `json:"voice,optional"`
Notes string `json:"notes,optional"`
}
PersonaIdPath {
Id string `path:"id"`
}
PersonaActiveData {
Id string `json:"id"`
}
PersonaSetActiveReq {
Id string `json:"id"`
}
PersonaAnalyzeTextReq {
Id string `path:"id"`
RawText string `json:"raw_text"`
SourceLabel string `json:"source_label,optional"`
}
PersonaAnalyzeAccountReq {
Id string `path:"id"`
Username string `json:"username"`
}
// --- Plays ---
ExternalTargetPublic {
Url string `json:"url"`
RawUrl string `json:"raw_url,optional"`
Shortcode string `json:"shortcode,optional"`
AuthorUsername string `json:"author_username,optional"`
TextPreview string `json:"text_preview,optional"`
MediaId string `json:"media_id,optional"`
ResolvedAt int64 `json:"resolved_at,optional"`
}
PlayStepPublic {
Id string `json:"id"`
SortOrder int `json:"sort_order"`
Kind string `json:"kind"`
AccountId string `json:"account_id"`
Text string `json:"text"`
DelayFromPreviousSec int `json:"delay_from_previous_sec"`
PersonaId string `json:"persona_id,optional"`
BrandId string `json:"brand_id,optional"`
ImageUrls []string `json:"image_urls,optional"`
}
PlayPublic {
Id string `json:"id"`
Title string `json:"title"`
Topic string `json:"topic"`
Status string `json:"status"`
LeadAccountId string `json:"lead_account_id"`
CastAccountIds []string `json:"cast_account_ids"`
TargetOwnPostId string `json:"target_own_post_id,optional"`
TargetExternal ExternalTargetPublic `json:"target_external,optional"`
Steps []PlayStepPublic `json:"steps"`
ScheduleStartAt int64 `json:"schedule_start_at"`
IntervalMinutes int `json:"interval_minutes,optional"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
PlayListData {
List []PlayPublic `json:"list"`
}
PlaySaveReq {
Id string `json:"id,optional"`
Title string `json:"title"`
Topic string `json:"topic,optional"`
Status string `json:"status,optional"`
LeadAccountId string `json:"lead_account_id"`
CastAccountIds []string `json:"cast_account_ids,optional"`
TargetOwnPostId string `json:"target_own_post_id,optional"`
TargetExternal ExternalTargetPublic `json:"target_external,optional"`
Steps []PlayStepPublic `json:"steps"`
ScheduleStartAt int64 `json:"schedule_start_at,optional"`
IntervalMinutes int `json:"interval_minutes,optional"`
}
PlayIdPath {
Id string `path:"id"`
}
PlayByPostReq {
OwnPostId string `form:"own_post_id"`
}
PlayByExternalReq {
Url string `form:"url"`
}
PlayResolveReq {
Url string `json:"url"`
}
// AI 產劇本一步(真 LLM互回串場共用
PlayGenerateStepReq {
PersonaId string `json:"persona_id,optional"`
Context string `json:"context"` // 主貼/上一則/目標文
Topic string `json:"topic,optional"`
SpeakerLabel string `json:"speaker_label,optional"`
IsLead bool `json:"is_lead,optional"`
// mode: reply預設| root串場主貼
Mode string `json:"mode,optional"`
}
PlayGenerateStepData {
Text string `json:"text"`
}
// 一次產完整劇本(背景 job
PlayGenerateScriptData {
JobId string `json:"job_id"`
Async bool `json:"async"`
}
// --- Outbox ---
OutboxStepPublic {
Id string `json:"id"`
StepId string `json:"step_id"`
SortOrder int `json:"sort_order"`
Kind string `json:"kind"`
AccountId string `json:"account_id"`
Text string `json:"text"`
Status string `json:"status"`
Error string `json:"error,optional"`
ScheduledAt int64 `json:"scheduled_at,optional"`
PublishedAt int64 `json:"published_at,optional"`
}
OutboxPublic {
Id string `json:"id"`
PlayId string `json:"play_id"`
Title string `json:"title"`
Status string `json:"status"`
Steps []OutboxStepPublic `json:"steps"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
OutboxListData {
List []OutboxPublic `json:"list"`
}
OutboxIdPath {
Id string `path:"id"`
}
OutboxRetryPath {
Id string `path:"id"`
StepId string `path:"stepId"`
}
// --- Compose ---
ComposeMimicReq {
SourceText string `json:"source_text"`
PersonaId string `json:"persona_id,optional"`
// 可選:從「我的貼文」結構分析帶過來的備註(鉤子/結構/可複製點)
StructureNotes string `json:"structure_notes,optional"`
}
// 仿寫改背景 Job立刻回 job_id完成後從 job.payload.result_text 取文
ComposeMimicData {
// 相容同步路徑才有Job 路徑為空
Text string `json:"text,optional"`
JobId string `json:"job_id,optional"`
Async bool `json:"async,optional"`
}
// 人設試產:依指紋真 LLM 產主貼 + 回文(可帶新聞話題)
ComposePersonaPreviewReq {
PersonaId string `json:"persona_id"`
// 手動話題;空則嘗試上網取新聞靈感
Topic string `json:"topic,optional"`
// 預設 true無 topic 時抓即時新聞標題當靈感
UseNews bool `json:"use_news,optional"`
}
ComposePersonaPreviewData {
Topic string `json:"topic"`
TopicSource string `json:"topic_source"` // news | manual | fallback
PostText string `json:"post_text"`
ReplyText string `json:"reply_text"`
Notes string `json:"notes,optional"`
}
ComposeViralReq {
Text string `json:"text"`
}
ViralAnalysisPublic {
Hooks string `json:"hooks"`
Structure string `json:"structure"`
Emotion string `json:"emotion"`
Summary string `json:"summary"`
Cta string `json:"cta,optional"`
Copyable string `json:"copyable,optional"`
Risks string `json:"risks,optional"`
}
ComposePublishReq {
AccountId string `json:"account_id"`
Text string `json:"text"`
Title string `json:"title,optional"`
ImageUrls []string `json:"image_urls,optional"`
ScheduleStartAt int64 `json:"schedule_start_at,optional"`
// Threads 話題標籤topic_tag150 字,可不加 #
TopicTag string `json:"topic_tag,optional"`
}
// --- Own posts ---
OwnPostReplyPublic {
Id string `json:"id"`
Username string `json:"username"`
Text string `json:"text"`
CreatedAt int64 `json:"created_at"`
LikeCount int `json:"like_count,optional"`
ReplyStatus string `json:"reply_status,optional"`
RepliedBy string `json:"replied_by,optional"`
RepliedAt int64 `json:"replied_at,optional"`
ParentReplyId string `json:"parent_reply_id,optional"`
IsMine bool `json:"is_mine,optional"`
}
OwnPostPublic {
Id string `json:"id"`
AccountId string `json:"account_id"`
MediaId string `json:"media_id,optional"`
Text string `json:"text"`
MediaType string `json:"media_type"`
MediaUrl string `json:"media_url,optional"`
ThumbnailUrl string `json:"thumbnail_url,optional"`
Permalink string `json:"permalink,optional"`
Shortcode string `json:"shortcode,optional"`
TopicTag string `json:"topic_tag,optional"`
LikeCount int `json:"like_count"`
ReplyCount int `json:"reply_count"`
RepostCount int `json:"repost_count"`
QuoteCount int `json:"quote_count"`
ViewCount int `json:"view_count"`
ShareCount int `json:"share_count"`
InsightsStatus string `json:"insights_status,optional"`
FormulaSummary string `json:"formula_summary,optional"`
Insight string `json:"insight,optional"`
FormulaDetail string `json:"formula_detail,optional"`
Replies []OwnPostReplyPublic `json:"replies"`
PublishedAt int64 `json:"published_at"`
}
OwnPostListData {
List []OwnPostPublic `json:"list"`
}
OwnPostListReq {
AccountId string `form:"account_id,optional"`
}
OwnPostSyncReq {
AccountId string `json:"account_id"`
}
OwnPostLastSyncedData {
LastSyncedAt int64 `json:"last_synced_at"`
}
OwnPostGenerateReplyReq {
PostId string `json:"post_id"`
ReplyId string `json:"reply_id,optional"`
PersonaId string `json:"persona_id,optional"`
}
OwnPostGenerateReplyData {
Text string `json:"text"`
}
OwnPostSendReplyReq {
PostId string `json:"post_id"`
ReplyId string `json:"reply_id,optional"`
Text string `json:"text"`
AccountId string `json:"account_id,optional"`
ImageUrls []string `json:"image_urls,optional"`
}
OwnPostAnalyzeReq {
PostId string `json:"post_id"`
}
// 點開貼文後再載留言(避免 sync 一次打爆 conversation API
OwnPostLoadRepliesReq {
PostId string `json:"post_id"`
}
// --- Mentions ---
MentionPublic {
Id string `json:"id"`
AccountId string `json:"account_id"`
MediaId string `json:"media_id,optional"`
FromUsername string `json:"from_username"`
Text string `json:"text"`
ContextSnippet string `json:"context_snippet"`
Permalink string `json:"permalink,optional"`
Status string `json:"status"`
DraftText string `json:"draft_text,optional"`
CreatedAt int64 `json:"created_at"`
}
MentionListData {
List []MentionPublic `json:"list"`
}
MentionListReq {
AccountId string `form:"account_id,optional"`
}
MentionSyncReq {
AccountId string `json:"account_id"`
}
MentionIdPath {
Id string `path:"id"`
}
MentionGenerateReq {
Id string `path:"id"`
PersonaId string `json:"persona_id,optional"`
}
MentionMarkRepliedReq {
Id string `path:"id"`
Text string `json:"text,optional"`
ImageUrls []string `json:"image_urls,optional"`
}
)
@server (
jwt: Auth
group: personas
prefix: /api/v1/personas
middleware: AuthJWT
)
service gateway {
@handler ListPersonas
get / returns (PersonaListData)
@handler GetActivePersona
get /active returns (PersonaActiveData)
@handler SetActivePersona
post /active (PersonaSetActiveReq) returns (PersonaActiveData)
@handler GetPersona
get /:id (PersonaIdPath) returns (PersonaPublic)
@handler SavePersona
post / (PersonaSaveReq) returns (PersonaPublic)
@handler RemovePersona
delete /:id (PersonaIdPath) returns (OkData)
@handler AnalyzePersonaText
post /:id/analyze-text (PersonaAnalyzeTextReq) returns (PersonaPublic)
@handler AnalyzePersonaAccount
post /:id/analyze-account (PersonaAnalyzeAccountReq) returns (PersonaPublic)
}
@server (
jwt: Auth
group: plays
prefix: /api/v1/plays
middleware: AuthJWT
)
service gateway {
@handler ListPlays
get / returns (PlayListData)
@handler ListPlaysByPost
get /by-post (PlayByPostReq) returns (PlayListData)
@handler ListPlaysByExternal
get /by-external (PlayByExternalReq) returns (PlayListData)
@handler ResolveExternalLink
post /resolve-external (PlayResolveReq) returns (ExternalTargetPublic)
@handler GetPlay
get /:id (PlayIdPath) returns (PlayPublic)
@handler SavePlay
post / (PlaySaveReq) returns (PlayPublic)
@handler RemovePlay
delete /:id (PlayIdPath) returns (OkData)
@handler SubmitPlay
post /:id/submit (PlayIdPath) returns (OutboxPublic)
@handler GeneratePlayStep
post /generate-step (PlayGenerateStepReq) returns (PlayGenerateStepData)
@handler GeneratePlayScript
post /:id/generate-script (PlayIdPath) returns (PlayGenerateScriptData)
}
@server (
jwt: Auth
group: outbox
prefix: /api/v1/outbox
middleware: AuthJWT
)
service gateway {
@handler ListOutbox
get / returns (OutboxListData)
@handler GetOutbox
get /:id (OutboxIdPath) returns (OutboxPublic)
@handler RemoveOutbox
delete /:id (OutboxIdPath) returns (OkData)
@handler RetryOutboxStep
post /:id/steps/:stepId/retry (OutboxRetryPath) returns (OutboxPublic)
}
@server (
jwt: Auth
group: compose
prefix: /api/v1/compose
middleware: AuthJWT
)
service gateway {
@handler ComposeMimic
post /mimic (ComposeMimicReq) returns (ComposeMimicData)
@handler ComposePersonaPreview
post /persona-preview (ComposePersonaPreviewReq) returns (ComposePersonaPreviewData)
@handler ComposeAnalyzeViral
post /analyze-viral (ComposeViralReq) returns (ViralAnalysisPublic)
@handler ComposePublishSingle
post /publish-single (ComposePublishReq) returns (OutboxPublic)
}
@server (
jwt: Auth
group: ownposts
prefix: /api/v1/own-posts
middleware: AuthJWT
)
service gateway {
@handler ListOwnPosts
get / (OwnPostListReq) returns (OwnPostListData)
@handler OwnPostsLastSynced
get /last-synced-at returns (OwnPostLastSyncedData)
@handler SyncOwnPosts
post /sync (OwnPostSyncReq) returns (OwnPostListData)
@handler OwnPostGenerateReply
post /generate-reply (OwnPostGenerateReplyReq) returns (OwnPostGenerateReplyData)
@handler OwnPostSendReply
post /send-reply (OwnPostSendReplyReq) returns (OwnPostPublic)
@handler OwnPostAnalyze
post /analyze (OwnPostAnalyzeReq) returns (OwnPostPublic)
@handler OwnPostLoadReplies
post /load-replies (OwnPostLoadRepliesReq) returns (OwnPostPublic)
}
@server (
jwt: Auth
group: mentions
prefix: /api/v1/mentions
middleware: AuthJWT
)
service gateway {
@handler ListMentions
get / (MentionListReq) returns (MentionListData)
@handler SyncMentions
post /sync (MentionSyncReq) returns (MentionListData)
@handler MentionGenerateReply
post /:id/generate-reply (MentionGenerateReq) returns (MentionPublic)
@handler MentionMarkReplied
post /:id/mark-replied (MentionMarkRepliedReq) returns (MentionPublic)
@handler MentionSkip
post /:id/skip (MentionIdPath) returns (MentionPublic)
}

View File

@ -0,0 +1,97 @@
syntax = "v1"
type (
ThreadsAccountPublic {
Id string `json:"id"`
Username string `json:"username"`
DisplayName string `json:"display_name"`
Connection string `json:"connection"` // connected|error|disconnected
IsUsable bool `json:"is_usable"`
AvatarColor string `json:"avatar_color,optional"`
AvatarUrl string `json:"avatar_url,optional"`
ErrorMessage string `json:"error_message,optional"`
SessionExpiresAt int64 `json:"session_expires_at,optional"`
SessionRefreshedAt int64 `json:"session_refreshed_at,optional"`
// never expose access_token / refresh_token
}
ThreadsAccountListData {
List []ThreadsAccountPublic `json:"list"`
}
ThreadsOAuthStartData {
AuthorizeUrl string `json:"authorize_url"`
State string `json:"state"`
}
// Callback is query-string from Meta / fake provider (public).
ThreadsOAuthCallbackReq {
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"`
}
ThreadsOAuthCallbackData {
Ok bool `json:"ok"`
Message string `json:"message,optional"`
// RedirectURL for custom handler (browser flow)
RedirectUrl string `json:"redirect_url,optional"`
}
ThreadsAccountIdPath {
Id string `path:"id"`
}
// 擴充(含舊版)匯入 Chrome storageState寫入 scout crawler-session 供 dev 模式海巡
ImportThreadsAccountSessionReq {
// path id + body
Id string `path:"id"`
StorageState string `json:"storageState"`
}
ImportThreadsAccountSessionData {
Success bool `json:"success"`
Valid bool `json:"valid"`
Synced bool `json:"synced"`
AccountId string `json:"account_id"`
Username string `json:"username,optional"`
Message string `json:"message"`
UpdateAt int64 `json:"update_at,optional"`
}
)
// Public OAuth callback (no JWT)
@server (
group: threads
prefix: /api/v1/threads-accounts
)
service gateway {
@handler ThreadsOAuthCallback
get /oauth/callback (ThreadsOAuthCallbackReq) returns (ThreadsOAuthCallbackData)
}
@server (
jwt: Auth
group: threads
prefix: /api/v1/threads-accounts
middleware: AuthJWT
)
service gateway {
@handler ThreadsOAuthStart
get /oauth/start returns (ThreadsOAuthStartData)
@handler ListThreadsAccounts
get / returns (ThreadsAccountListData)
@handler RefreshThreadsAccount
post /:id/refresh (ThreadsAccountIdPath) returns (ThreadsAccountPublic)
// Chrome 擴充:匯入 Playwright storageState舊路徑相容實際存 crawler session
@handler ImportThreadsAccountSession
post /:id/session/import (ImportThreadsAccountSessionReq) returns (ImportThreadsAccountSessionData)
@handler DisconnectThreadsAccount
delete /:id (ThreadsAccountIdPath) returns (OkData)
}

View File

@ -0,0 +1,141 @@
syntax = "v1"
type (
UsageEventPublic {
Id string `json:"id"`
Uid int64 `json:"uid"`
Meter string `json:"meter"`
Credits int `json:"credits"`
KeyMode string `json:"key_mode"`
Label string `json:"label"`
Source string `json:"source"`
CreatedAt int64 `json:"created_at"`
}
UsageMeterCount {
Count int `json:"count"`
Credits int `json:"credits"`
}
UsagePlatformBlock {
CreditsUsed int `json:"credits_used"`
CreditsRemaining int `json:"credits_remaining"`
CreditsTotal int `json:"credits_total"`
CallCount int `json:"call_count"`
ByMeter map[string]UsageMeterCount `json:"by_meter,optional"`
}
UsageByokBlock {
CallCount int `json:"call_count"`
ByMeter map[string]UsageMeterCount `json:"by_meter,optional"`
}
UsageSummaryData {
MonthKey string `json:"month_key"`
Uid int64 `json:"uid"`
PlanId string `json:"plan_id"`
Unlimited bool `json:"unlimited"`
Platform UsagePlatformBlock `json:"platform"`
Byok UsageByokBlock `json:"byok"`
// FE progress bar (platform only)
TotalCredits int `json:"total_credits"`
RemainingCredits int `json:"remaining_credits"`
}
UsageSummaryReq {
MonthKey string `form:"month_key,optional"`
}
UsageEventsReq {
MonthKey string `form:"month_key,optional"`
KeyMode string `form:"key_mode,optional"` // platform|byok|all
Limit int `form:"limit,optional"`
}
UsageEventsData {
List []UsageEventPublic `json:"list"`
}
UsagePrefsData {
PlanId string `json:"plan_id"`
Unlimited bool `json:"unlimited"`
}
UsageSetPrefsReq {
Uid string `json:"uid"`
PlanId string `json:"plan_id,optional"`
Unlimited *bool `json:"unlimited,optional"`
}
UsagePurchaseReq {
PlanId string `json:"plan_id"`
MockRef string `json:"mock_ref,optional"`
}
UsagePurchasePublic {
Id string `json:"id"`
PlanId string `json:"plan_id"`
MockRef string `json:"mock_ref,optional"`
CreatedAt int64 `json:"created_at"`
}
UsagePurchasesData {
List []UsagePurchasePublic `json:"list"`
}
UsageTenantRow {
Uid int64 `json:"uid"`
PlanId string `json:"plan_id"`
Unlimited bool `json:"unlimited"`
PlatformCreditsUsed int `json:"platform_credits_used"`
ByokCallCount int `json:"byok_call_count"`
}
UsageTenantSummaryData {
MonthKey string `json:"month_key"`
PlatformCreditsUsed int `json:"platform_credits_used"`
ByokCallCount int `json:"byok_call_count"`
Members []UsageTenantRow `json:"members"`
}
UsageTenantSummaryReq {
MonthKey string `form:"month_key,optional"`
}
)
@server (
jwt: Auth
group: usage
prefix: /api/v1/usage
middleware: AuthJWT
)
service gateway {
@handler GetUsageSummary
get /summary (UsageSummaryReq) returns (UsageSummaryData)
@handler ListUsageEvents
get /events (UsageEventsReq) returns (UsageEventsData)
@handler GetUsagePrefs
get /prefs returns (UsagePrefsData)
@handler PurchasePlan
post /purchase (UsagePurchaseReq) returns (UsagePurchasePublic)
@handler ListMyPurchases
get /purchases returns (UsagePurchasesData)
}
@server (
jwt: Auth
group: usage
prefix: /api/v1/usage
middleware: AuthJWT,AdminAuth
)
service gateway {
@handler SetUsagePrefs
post /prefs (UsageSetPrefsReq) returns (UsagePrefsData)
@handler GetTenantUsageSummary
get /tenant-summary (UsageTenantSummaryReq) returns (UsageTenantSummaryData)
}

View File

@ -27,7 +27,12 @@ type Config struct {
RefreshExpire int64 RefreshExpire int64
} }
Platform struct { Platform struct {
// AIKey — legacy alias for XAIKey
AIKey string `json:",optional"` AIKey string `json:",optional"`
// XAIKey — platform BYOK for xAI (api.x.ai)
XAIKey string `json:",optional"`
// OpenCodeKey — platform BYOK for OpenCode Go (opencode.ai/zen/go)
OpenCodeKey string `json:",optional"`
ExaKey string `json:",optional"` ExaKey string `json:",optional"`
ThreadsAppId string `json:",optional"` ThreadsAppId string `json:",optional"`
ThreadsAppSecret string `json:",optional"` ThreadsAppSecret string `json:",optional"`
@ -62,6 +67,10 @@ type Config struct {
// PublicWebBase — 前端 origin用於重設密碼信內連結logo勿尾斜線 // PublicWebBase — 前端 origin用於重設密碼信內連結logo勿尾斜線
// 例http://127.0.0.1:5173 或 https://threads-tool-dev.30cm.net // 例http://127.0.0.1:5173 或 https://threads-tool-dev.30cm.net
PublicWebBase string `json:",optional,default=http://127.0.0.1:5173"` PublicWebBase string `json:",optional,default=http://127.0.0.1:5173"`
// PublicAPIBase — 對外 API originThreads OAuth redirect_uri 必須 https 公開網址)
// 空則:若 PublicWebBase 為 https 則同 host否則回落 http://127.0.0.1:Port
// 例https://threads-tool-dev.30cm.net
PublicAPIBase string `json:",optional"`
// Brand — 郵件 chromehermesLogoURL 空則用 PublicWebBase + /brand-mark.jpg // Brand — 郵件 chromehermesLogoURL 空則用 PublicWebBase + /brand-mark.jpg
Brand struct { Brand struct {
Name string `json:",optional,default=Harbor Desk"` Name string `json:",optional,default=Harbor Desk"`
@ -111,9 +120,25 @@ func (c *Config) ApplyEnv() {
if v := os.Getenv("PLATFORM_AI_KEY"); v != "" { if v := os.Getenv("PLATFORM_AI_KEY"); v != "" {
c.Platform.AIKey = v c.Platform.AIKey = v
} }
if v := firstEnv("PLATFORM_XAI_KEY", "XAI_API_KEY"); v != "" {
c.Platform.XAIKey = v
}
if v := firstEnv("PLATFORM_OPENCODE_KEY", "OPENCODE_API_KEY", "OPENCODE_GO_API_KEY"); v != "" {
c.Platform.OpenCodeKey = v
}
// legacy AIKey → XAI when XAI empty
if c.Platform.XAIKey == "" && c.Platform.AIKey != "" {
c.Platform.XAIKey = c.Platform.AIKey
}
if v := os.Getenv("PLATFORM_EXA_KEY"); v != "" { if v := os.Getenv("PLATFORM_EXA_KEY"); v != "" {
c.Platform.ExaKey = v c.Platform.ExaKey = v
} }
if v := firstEnv("THREADS_APP_ID", "HAIXUN_THREADS_APP_ID"); v != "" {
c.Platform.ThreadsAppId = v
}
if v := firstEnv("THREADS_APP_SECRET", "HAIXUN_THREADS_APP_SECRET"); v != "" {
c.Platform.ThreadsAppSecret = v
}
if v := os.Getenv("WORKER_ID"); v != "" { if v := os.Getenv("WORKER_ID"); v != "" {
c.Worker.ID = v c.Worker.ID = v
} }
@ -155,6 +180,9 @@ func (c *Config) ApplyEnv() {
if v := firstEnv("PUBLIC_WEB_BASE", "HAIXUN_PUBLIC_WEB_BASE"); v != "" { if v := firstEnv("PUBLIC_WEB_BASE", "HAIXUN_PUBLIC_WEB_BASE"); v != "" {
c.PublicWebBase = strings.TrimRight(v, "/") c.PublicWebBase = strings.TrimRight(v, "/")
} }
if v := firstEnv("PUBLIC_API_BASE", "HAIXUN_PUBLIC_API_BASE"); v != "" {
c.PublicAPIBase = strings.TrimRight(v, "/")
}
if v := os.Getenv("MAIL_BRAND_NAME"); v != "" { if v := os.Getenv("MAIL_BRAND_NAME"); v != "" {
c.Brand.Name = v c.Brand.Name = v
} }

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package compose
import (
"net/http"
"apps/backend/internal/logic/compose"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ComposeAnalyzeViralHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ComposeViralReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := compose.NewComposeAnalyzeViralLogic(r.Context(), svcCtx)
data, err := l.ComposeAnalyzeViral(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package compose
import (
"net/http"
"apps/backend/internal/logic/compose"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ComposeMimicHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ComposeMimicReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := compose.NewComposeMimicLogic(r.Context(), svcCtx)
data, err := l.ComposeMimic(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,26 @@
package compose
import (
"net/http"
"apps/backend/internal/logic/compose"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ComposePersonaPreviewHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ComposePersonaPreviewReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := compose.NewComposePersonaPreviewLogic(r.Context(), svcCtx)
data, err := l.ComposePersonaPreview(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package compose
import (
"net/http"
"apps/backend/internal/logic/compose"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ComposePublishSingleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ComposePublishReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := compose.NewComposePublishSingleLogic(r.Context(), svcCtx)
data, err := l.ComposePublishSingle(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,34 @@
package extension
import (
"net/http"
"os"
"path/filepath"
"apps/backend/internal/logic/extension"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func DownloadExtensionZipHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := extension.NewDownloadExtensionZipLogic(r.Context(), svcCtx)
if _, err := l.DownloadExtensionZip(); err != nil {
response.Write(r.Context(), w, nil, err)
return
}
path := svcCtx.ExtensionZipPath
if path == "" {
path = extension.ResolveZipPath(svcCtx)
}
w.Header().Set("Content-Type", "application/zip")
w.Header().Set("Content-Disposition", `attachment; filename="haixun-threads-sync.zip"`)
http.ServeFile(w, r, filepath.Clean(path))
}
}
// ensure zip exists for tests
func zipExists(p string) bool {
st, err := os.Stat(p)
return err == nil && st.Size() > 0
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ActivateInspireSessionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.InspireSessionIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := inspire.NewActivateInspireSessionLogic(r.Context(), svcCtx)
data, err := l.ActivateInspireSession(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,107 @@
package inspire
import (
"encoding/json"
"net/http"
"strings"
"apps/backend/internal/middleware"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/rest/httpx"
)
// ChatStreamHandler — SSE 串流靈感聊天/產文。
// 事件(每行 data: JSON
//
// {"type":"delta","text":"..."}
// {"type":"done","session":{...},"message_id":"..."}
// {"type":"error","message":"..."}
func ChatStreamHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.InspireChatReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
uid, ok := middleware.UIDFrom(r.Context())
if !ok || uid <= 0 {
httpx.ErrorCtx(r.Context(), w, response.Biz(401, 401001, "missing authorization"))
return
}
if svcCtx.Inspire == nil {
httpx.ErrorCtx(r.Context(), w, response.Biz(503, 503001, "inspire not configured"))
return
}
flusher, ok := w.(http.Flusher)
if !ok {
httpx.ErrorCtx(r.Context(), w, response.Biz(500, 500000, "streaming unsupported"))
return
}
w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
w.Header().Set("Cache-Control", "no-cache, no-transform")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no")
w.WriteHeader(http.StatusOK)
flusher.Flush()
writeEvent := func(v any) bool {
b, err := json.Marshal(v)
if err != nil {
return false
}
if _, err := w.Write([]byte("data: ")); err != nil {
return false
}
if _, err := w.Write(b); err != nil {
return false
}
if _, err := w.Write([]byte("\n\n")); err != nil {
return false
}
flusher.Flush()
return true
}
mode := strings.TrimSpace(req.Mode)
if mode == "" {
mode = "chat"
}
out, err := svcCtx.Inspire.ChatStream(r.Context(), uid, req.Message, req.PinnedIds, mode, req.PersonaId, req.SessionId, req.Material, func(chunk string) error {
if chunk == "" {
return nil
}
if !writeEvent(map[string]any{"type": "delta", "text": chunk}) {
return http.ErrAbortHandler // client gone
}
return nil
})
if err != nil {
_ = writeEvent(map[string]any{"type": "error", "message": err.Error()})
return
}
sess := out.Session
pub := types.SessionFromDomain(sess)
msgID := ""
if len(sess.Messages) > 0 {
msgID = sess.Messages[len(sess.Messages)-1].ID
}
// 回傳實際送 AI 的 prompt 指紋/全文,供前端與預覽對照
_ = writeEvent(map[string]any{
"type": "done",
"session": pub,
"message_id": msgID,
"prompt": out.Prompt,
"prompt_fingerprint": out.Fingerprint,
"prompt_char_count": out.CharCount,
"prompt_rune_count": out.RuneCount,
"prompt_sections": out.Sections,
})
logx.WithContext(r.Context()).Infof("inspire chat-stream ok uid=%d mode=%s fp=%s chars=%d", uid, mode, out.Fingerprint, out.CharCount)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func ClearInspireSessionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := inspire.NewClearInspireSessionLogic(r.Context(), svcCtx)
data, err := l.ClearInspireSession()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func CreateInspireSessionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.InspireSessionCreateReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := inspire.NewCreateInspireSessionLogic(r.Context(), svcCtx)
data, err := l.CreateInspireSession(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func DeleteInspireSessionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.InspireSessionIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := inspire.NewDeleteInspireSessionLogic(r.Context(), svcCtx)
data, err := l.DeleteInspireSession(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetInspireSessionByIdHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.InspireSessionIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := inspire.NewGetInspireSessionByIdLogic(r.Context(), svcCtx)
data, err := l.GetInspireSessionById(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func GetInspireSessionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := inspire.NewGetInspireSessionLogic(r.Context(), svcCtx)
data, err := l.GetInspireSession()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func InspireChatHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.InspireChatReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := inspire.NewInspireChatLogic(r.Context(), svcCtx)
data, err := l.InspireChat(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func InspirePromptPreviewHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.InspirePromptPreviewReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := inspire.NewInspirePromptPreviewLogic(r.Context(), svcCtx)
data, err := l.InspirePromptPreview(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func ListInspireElementsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := inspire.NewListInspireElementsLogic(r.Context(), svcCtx)
data, err := l.ListInspireElements()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func ListInspireSessionsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := inspire.NewListInspireSessionsLogic(r.Context(), svcCtx)
data, err := l.ListInspireSessions()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func ListTrendsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := inspire.NewListTrendsLogic(r.Context(), svcCtx)
data, err := l.ListTrends()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func RefreshTrendsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := inspire.NewRefreshTrendsLogic(r.Context(), svcCtx)
data, err := l.RefreshTrends()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func RemoveInspireElementHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.InspireElementIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := inspire.NewRemoveInspireElementLogic(r.Context(), svcCtx)
data, err := l.RemoveInspireElement(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package inspire
import (
"net/http"
"apps/backend/internal/logic/inspire"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func SaveInspireElementHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.InspireElementSaveReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := inspire.NewSaveInspireElementLogic(r.Context(), svcCtx)
data, err := l.SaveInspireElement(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
package handler
import (
"net/http"
inspireHandler "apps/backend/internal/handler/inspire"
"apps/backend/internal/svc"
"github.com/zeromicro/go-zero/rest"
)
// RegisterInspireStream 註冊靈感 SSE 路由(手寫,避開 goctl JSON envelope
func RegisterInspireStream(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/chat-stream",
Handler: inspireHandler.ChatStreamHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/inspire"),
)
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package jobs
import (
"net/http"
"apps/backend/internal/logic/jobs"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func DeleteJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.JobIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := jobs.NewDeleteJobLogic(r.Context(), svcCtx)
data, err := l.DeleteJob(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package jobs
import (
"net/http"
"apps/backend/internal/logic/jobs"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.JobIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := jobs.NewGetJobLogic(r.Context(), svcCtx)
data, err := l.GetJob(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package jobs
import (
"net/http"
"apps/backend/internal/logic/jobs"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func ListJobsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := jobs.NewListJobsLogic(r.Context(), svcCtx)
data, err := l.ListJobs()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package jobs
import (
"net/http"
"apps/backend/internal/logic/jobs"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func StartDemoJobHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := jobs.NewStartDemoJobLogic(r.Context(), svcCtx)
data, err := l.StartDemoJob()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package media
import (
"net/http"
"apps/backend/internal/logic/media"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GenerateImageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GenerateImageReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := media.NewGenerateImageLogic(r.Context(), svcCtx)
data, err := l.GenerateImage(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package mentions
import (
"net/http"
"apps/backend/internal/logic/mentions"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ListMentionsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.MentionListReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := mentions.NewListMentionsLogic(r.Context(), svcCtx)
data, err := l.ListMentions(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package mentions
import (
"net/http"
"apps/backend/internal/logic/mentions"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func MentionGenerateReplyHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.MentionGenerateReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := mentions.NewMentionGenerateReplyLogic(r.Context(), svcCtx)
data, err := l.MentionGenerateReply(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package mentions
import (
"net/http"
"apps/backend/internal/logic/mentions"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func MentionMarkRepliedHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.MentionMarkRepliedReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := mentions.NewMentionMarkRepliedLogic(r.Context(), svcCtx)
data, err := l.MentionMarkReplied(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package mentions
import (
"net/http"
"apps/backend/internal/logic/mentions"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func MentionSkipHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.MentionIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := mentions.NewMentionSkipLogic(r.Context(), svcCtx)
data, err := l.MentionSkip(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package mentions
import (
"net/http"
"apps/backend/internal/logic/mentions"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func SyncMentionsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.MentionSyncReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := mentions.NewSyncMentionsLogic(r.Context(), svcCtx)
data, err := l.SyncMentions(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package notifications
import (
"net/http"
"apps/backend/internal/logic/notifications"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func ListNotificationsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := notifications.NewListNotificationsLogic(r.Context(), svcCtx)
data, err := l.ListNotifications()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package notifications
import (
"net/http"
"apps/backend/internal/logic/notifications"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func MarkAllNotificationsReadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := notifications.NewMarkAllNotificationsReadLogic(r.Context(), svcCtx)
data, err := l.MarkAllNotificationsRead()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package notifications
import (
"net/http"
"apps/backend/internal/logic/notifications"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func MarkNotificationReadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.NotificationIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := notifications.NewMarkNotificationReadLogic(r.Context(), svcCtx)
data, err := l.MarkNotificationRead(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package notifications
import (
"net/http"
"apps/backend/internal/logic/notifications"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func UnreadNotificationCountHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := notifications.NewUnreadNotificationCountLogic(r.Context(), svcCtx)
data, err := l.UnreadNotificationCount()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package outbox
import (
"net/http"
"apps/backend/internal/logic/outbox"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetOutboxHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OutboxIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := outbox.NewGetOutboxLogic(r.Context(), svcCtx)
data, err := l.GetOutbox(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package outbox
import (
"net/http"
"apps/backend/internal/logic/outbox"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func ListOutboxHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := outbox.NewListOutboxLogic(r.Context(), svcCtx)
data, err := l.ListOutbox()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package outbox
import (
"net/http"
"apps/backend/internal/logic/outbox"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func RemoveOutboxHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OutboxIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := outbox.NewRemoveOutboxLogic(r.Context(), svcCtx)
data, err := l.RemoveOutbox(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package outbox
import (
"net/http"
"apps/backend/internal/logic/outbox"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func RetryOutboxStepHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OutboxRetryPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := outbox.NewRetryOutboxStepLogic(r.Context(), svcCtx)
data, err := l.RetryOutboxStep(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package ownposts
import (
"net/http"
"apps/backend/internal/logic/ownposts"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ListOwnPostsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OwnPostListReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := ownposts.NewListOwnPostsLogic(r.Context(), svcCtx)
data, err := l.ListOwnPosts(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package ownposts
import (
"net/http"
"apps/backend/internal/logic/ownposts"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func OwnPostAnalyzeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OwnPostAnalyzeReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := ownposts.NewOwnPostAnalyzeLogic(r.Context(), svcCtx)
data, err := l.OwnPostAnalyze(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package ownposts
import (
"net/http"
"apps/backend/internal/logic/ownposts"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func OwnPostGenerateReplyHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OwnPostGenerateReplyReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := ownposts.NewOwnPostGenerateReplyLogic(r.Context(), svcCtx)
data, err := l.OwnPostGenerateReply(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,26 @@
package ownposts
import (
"net/http"
"apps/backend/internal/logic/ownposts"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func OwnPostLoadRepliesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OwnPostLoadRepliesReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := ownposts.NewOwnPostLoadRepliesLogic(r.Context(), svcCtx)
data, err := l.OwnPostLoadReplies(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package ownposts
import (
"net/http"
"apps/backend/internal/logic/ownposts"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func OwnPostSendReplyHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OwnPostSendReplyReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := ownposts.NewOwnPostSendReplyLogic(r.Context(), svcCtx)
data, err := l.OwnPostSendReply(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package ownposts
import (
"net/http"
"apps/backend/internal/logic/ownposts"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func OwnPostsLastSyncedHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := ownposts.NewOwnPostsLastSyncedLogic(r.Context(), svcCtx)
data, err := l.OwnPostsLastSynced()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package ownposts
import (
"net/http"
"apps/backend/internal/logic/ownposts"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func SyncOwnPostsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.OwnPostSyncReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := ownposts.NewSyncOwnPostsLogic(r.Context(), svcCtx)
data, err := l.SyncOwnPosts(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package personas
import (
"net/http"
"apps/backend/internal/logic/personas"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func AnalyzePersonaAccountHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PersonaAnalyzeAccountReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := personas.NewAnalyzePersonaAccountLogic(r.Context(), svcCtx)
data, err := l.AnalyzePersonaAccount(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package personas
import (
"net/http"
"apps/backend/internal/logic/personas"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func AnalyzePersonaTextHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PersonaAnalyzeTextReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := personas.NewAnalyzePersonaTextLogic(r.Context(), svcCtx)
data, err := l.AnalyzePersonaText(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package personas
import (
"net/http"
"apps/backend/internal/logic/personas"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func GetActivePersonaHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := personas.NewGetActivePersonaLogic(r.Context(), svcCtx)
data, err := l.GetActivePersona()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package personas
import (
"net/http"
"apps/backend/internal/logic/personas"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetPersonaHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PersonaIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := personas.NewGetPersonaLogic(r.Context(), svcCtx)
data, err := l.GetPersona(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package personas
import (
"net/http"
"apps/backend/internal/logic/personas"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func ListPersonasHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := personas.NewListPersonasLogic(r.Context(), svcCtx)
data, err := l.ListPersonas()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package personas
import (
"net/http"
"apps/backend/internal/logic/personas"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func RemovePersonaHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PersonaIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := personas.NewRemovePersonaLogic(r.Context(), svcCtx)
data, err := l.RemovePersona(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package personas
import (
"net/http"
"apps/backend/internal/logic/personas"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func SavePersonaHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PersonaSaveReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := personas.NewSavePersonaLogic(r.Context(), svcCtx)
data, err := l.SavePersona(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package personas
import (
"net/http"
"apps/backend/internal/logic/personas"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func SetActivePersonaHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PersonaSetActiveReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := personas.NewSetActivePersonaLogic(r.Context(), svcCtx)
data, err := l.SetActivePersona(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package plays
import (
"net/http"
"apps/backend/internal/logic/plays"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GeneratePlayScriptHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PlayIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := plays.NewGeneratePlayScriptLogic(r.Context(), svcCtx)
data, err := l.GeneratePlayScript(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package plays
import (
"net/http"
"apps/backend/internal/logic/plays"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GeneratePlayStepHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PlayGenerateStepReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := plays.NewGeneratePlayStepLogic(r.Context(), svcCtx)
data, err := l.GeneratePlayStep(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package plays
import (
"net/http"
"apps/backend/internal/logic/plays"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetPlayHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PlayIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := plays.NewGetPlayLogic(r.Context(), svcCtx)
data, err := l.GetPlay(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package plays
import (
"net/http"
"apps/backend/internal/logic/plays"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ListPlaysByExternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PlayByExternalReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := plays.NewListPlaysByExternalLogic(r.Context(), svcCtx)
data, err := l.ListPlaysByExternal(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package plays
import (
"net/http"
"apps/backend/internal/logic/plays"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ListPlaysByPostHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PlayByPostReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := plays.NewListPlaysByPostLogic(r.Context(), svcCtx)
data, err := l.ListPlaysByPost(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package plays
import (
"net/http"
"apps/backend/internal/logic/plays"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func ListPlaysHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := plays.NewListPlaysLogic(r.Context(), svcCtx)
data, err := l.ListPlays()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package plays
import (
"net/http"
"apps/backend/internal/logic/plays"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func RemovePlayHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PlayIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := plays.NewRemovePlayLogic(r.Context(), svcCtx)
data, err := l.RemovePlay(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package plays
import (
"net/http"
"apps/backend/internal/logic/plays"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ResolveExternalLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PlayResolveReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := plays.NewResolveExternalLinkLogic(r.Context(), svcCtx)
data, err := l.ResolveExternalLink(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package plays
import (
"net/http"
"apps/backend/internal/logic/plays"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func SavePlayHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PlaySaveReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := plays.NewSavePlayLogic(r.Context(), svcCtx)
data, err := l.SavePlay(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package plays
import (
"net/http"
"apps/backend/internal/logic/plays"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func SubmitPlayHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PlayIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := plays.NewSubmitPlayLogic(r.Context(), svcCtx)
data, err := l.SubmitPlay(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package proxy
import (
"net/http"
"apps/backend/internal/logic/proxy"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func AICompleteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AICompleteReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := proxy.NewAICompleteLogic(r.Context(), svcCtx)
data, err := l.AIComplete(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package proxy
import (
"net/http"
"apps/backend/internal/logic/proxy"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ExaSearchHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.SearchReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := proxy.NewExaSearchLogic(r.Context(), svcCtx)
data, err := l.ExaSearch(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package research
import (
"net/http"
"apps/backend/internal/logic/research"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ResearchSearchHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ResearchSearchReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := research.NewResearchSearchLogic(r.Context(), svcCtx)
data, err := l.ResearchSearch(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -8,9 +8,24 @@ import (
admin "apps/backend/internal/handler/admin" admin "apps/backend/internal/handler/admin"
auth "apps/backend/internal/handler/auth" auth "apps/backend/internal/handler/auth"
compose "apps/backend/internal/handler/compose"
extension "apps/backend/internal/handler/extension"
inspire "apps/backend/internal/handler/inspire"
jobs "apps/backend/internal/handler/jobs"
media "apps/backend/internal/handler/media" media "apps/backend/internal/handler/media"
mentions "apps/backend/internal/handler/mentions"
notifications "apps/backend/internal/handler/notifications"
outbox "apps/backend/internal/handler/outbox"
ownposts "apps/backend/internal/handler/ownposts"
personas "apps/backend/internal/handler/personas"
ping "apps/backend/internal/handler/ping" ping "apps/backend/internal/handler/ping"
plays "apps/backend/internal/handler/plays"
proxy "apps/backend/internal/handler/proxy"
research "apps/backend/internal/handler/research"
scout "apps/backend/internal/handler/scout"
settings "apps/backend/internal/handler/settings" settings "apps/backend/internal/handler/settings"
threads "apps/backend/internal/handler/threads"
usage "apps/backend/internal/handler/usage"
"apps/backend/internal/svc" "apps/backend/internal/svc"
"github.com/zeromicro/go-zero/rest" "github.com/zeromicro/go-zero/rest"
@ -171,6 +186,171 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithPrefix("/api/v1/auth"), rest.WithPrefix("/api/v1/auth"),
) )
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/analyze-viral",
Handler: compose.ComposeAnalyzeViralHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/mimic",
Handler: compose.ComposeMimicHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/persona-preview",
Handler: compose.ComposePersonaPreviewHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/publish-single",
Handler: compose.ComposePublishSingleHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/compose"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/download",
Handler: extension.DownloadExtensionZipHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/extension"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/chat",
Handler: inspire.InspireChatHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/elements",
Handler: inspire.ListInspireElementsHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/elements",
Handler: inspire.SaveInspireElementHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/elements/:id",
Handler: inspire.RemoveInspireElementHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/prompt-preview",
Handler: inspire.InspirePromptPreviewHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/session",
Handler: inspire.GetInspireSessionHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/session/clear",
Handler: inspire.ClearInspireSessionHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/sessions",
Handler: inspire.ListInspireSessionsHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/sessions",
Handler: inspire.CreateInspireSessionHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/sessions/:id",
Handler: inspire.GetInspireSessionByIdHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/sessions/:id",
Handler: inspire.DeleteInspireSessionHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/sessions/:id/activate",
Handler: inspire.ActivateInspireSessionHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/trends",
Handler: inspire.ListTrendsHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/trends/refresh",
Handler: inspire.RefreshTrendsHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/inspire"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/",
Handler: jobs.ListJobsHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/:id",
Handler: jobs.GetJobHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/:id",
Handler: jobs.DeleteJobHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/jobs"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT, serverCtx.AdminAuth},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/demo",
Handler: jobs.StartDemoJobHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/jobs"),
)
server.AddRoutes( server.AddRoutes(
rest.WithMiddlewares( rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT}, []rest.Middleware{serverCtx.AuthJWT},
@ -185,6 +365,211 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithPrefix("/api/v1/media"), rest.WithPrefix("/api/v1/media"),
) )
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/generate-image",
Handler: media.GenerateImageHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/media"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/",
Handler: mentions.ListMentionsHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/:id/generate-reply",
Handler: mentions.MentionGenerateReplyHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/:id/mark-replied",
Handler: mentions.MentionMarkRepliedHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/:id/skip",
Handler: mentions.MentionSkipHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/sync",
Handler: mentions.SyncMentionsHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/mentions"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/",
Handler: notifications.ListNotificationsHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/:id/read",
Handler: notifications.MarkNotificationReadHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/read-all",
Handler: notifications.MarkAllNotificationsReadHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/unread-count",
Handler: notifications.UnreadNotificationCountHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/notifications"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/",
Handler: outbox.ListOutboxHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/:id",
Handler: outbox.GetOutboxHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/:id",
Handler: outbox.RemoveOutboxHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/:id/steps/:stepId/retry",
Handler: outbox.RetryOutboxStepHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/outbox"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/",
Handler: ownposts.ListOwnPostsHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/analyze",
Handler: ownposts.OwnPostAnalyzeHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/generate-reply",
Handler: ownposts.OwnPostGenerateReplyHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/last-synced-at",
Handler: ownposts.OwnPostsLastSyncedHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/load-replies",
Handler: ownposts.OwnPostLoadRepliesHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/send-reply",
Handler: ownposts.OwnPostSendReplyHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/sync",
Handler: ownposts.SyncOwnPostsHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/own-posts"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/",
Handler: personas.ListPersonasHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/",
Handler: personas.SavePersonaHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/:id",
Handler: personas.GetPersonaHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/:id",
Handler: personas.RemovePersonaHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/:id/analyze-account",
Handler: personas.AnalyzePersonaAccountHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/:id/analyze-text",
Handler: personas.AnalyzePersonaTextHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/active",
Handler: personas.GetActivePersonaHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/active",
Handler: personas.SetActivePersonaHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/personas"),
)
server.AddRoutes( server.AddRoutes(
[]rest.Route{ []rest.Route{
{ {
@ -201,6 +586,246 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithPrefix("/api/v1"), rest.WithPrefix("/api/v1"),
) )
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/",
Handler: plays.ListPlaysHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/",
Handler: plays.SavePlayHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/:id",
Handler: plays.GetPlayHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/:id",
Handler: plays.RemovePlayHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/:id/generate-script",
Handler: plays.GeneratePlayScriptHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/:id/submit",
Handler: plays.SubmitPlayHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/by-external",
Handler: plays.ListPlaysByExternalHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/by-post",
Handler: plays.ListPlaysByPostHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/generate-step",
Handler: plays.GeneratePlayStepHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/resolve-external",
Handler: plays.ResolveExternalLinkHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/plays"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/ai/complete",
Handler: proxy.AICompleteHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/search",
Handler: proxy.ExaSearchHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/proxy"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/search",
Handler: research.ResearchSearchHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/research"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/brands",
Handler: scout.ListBrandsHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/brands",
Handler: scout.SaveBrandHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/brands-active",
Handler: scout.GetActiveBrandHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/brands-active",
Handler: scout.SetActiveBrandHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/brands/:id",
Handler: scout.GetBrandHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/brands/:id",
Handler: scout.RemoveBrandHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/brief",
Handler: scout.PrepareBriefHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/crawler-session",
Handler: scout.SetCrawlerSessionHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/crawler-session",
Handler: scout.ClearCrawlerSessionHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/homework",
Handler: scout.ListHomeworkHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/homework",
Handler: scout.SaveHomeworkHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/homework/:themeKey",
Handler: scout.GetHomeworkHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/homework/:themeKey",
Handler: scout.RemoveHomeworkHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/posts",
Handler: scout.ListScoutPostsHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/posts/:id",
Handler: scout.RemoveScoutPostHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/posts/:id/draft",
Handler: scout.DraftOutreachHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/posts/:id/mark-published",
Handler: scout.MarkPublishedHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/posts/:id/send",
Handler: scout.SendOutreachHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/posts/:id/skip",
Handler: scout.SkipOutreachHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/products",
Handler: scout.ListProductsHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/products",
Handler: scout.SaveProductHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/products/:id",
Handler: scout.GetProductHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/products/:id",
Handler: scout.RemoveProductHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/products/all",
Handler: scout.ListAllProductsHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/products/import",
Handler: scout.ImportProductHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/scan",
Handler: scout.RunScanHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/themes/:themeKey",
Handler: scout.RemoveScoutThemeHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/scout"),
)
server.AddRoutes( server.AddRoutes(
rest.WithMiddlewares( rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT}, []rest.Middleware{serverCtx.AuthJWT},
@ -230,8 +855,114 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/placement", Path: "/placement",
Handler: settings.SavePlacementHandler(serverCtx), Handler: settings.SavePlacementHandler(serverCtx),
}, },
{
Method: http.MethodGet,
Path: "/threads",
Handler: settings.GetThreadsHandler(serverCtx),
},
}..., }...,
), ),
rest.WithPrefix("/api/v1/settings"), rest.WithPrefix("/api/v1/settings"),
) )
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/oauth/callback",
Handler: threads.ThreadsOAuthCallbackHandler(serverCtx),
},
},
rest.WithPrefix("/api/v1/threads-accounts"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/",
Handler: threads.ListThreadsAccountsHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/:id",
Handler: threads.DisconnectThreadsAccountHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/:id/refresh",
Handler: threads.RefreshThreadsAccountHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/:id/session/import",
Handler: threads.ImportThreadsAccountSessionHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/oauth/start",
Handler: threads.ThreadsOAuthStartHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/threads-accounts"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/events",
Handler: usage.ListUsageEventsHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/prefs",
Handler: usage.GetUsagePrefsHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/purchase",
Handler: usage.PurchasePlanHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/purchases",
Handler: usage.ListMyPurchasesHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/summary",
Handler: usage.GetUsageSummaryHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/usage"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT, serverCtx.AdminAuth},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/prefs",
Handler: usage.SetUsagePrefsHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/tenant-summary",
Handler: usage.GetTenantUsageSummaryHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/usage"),
)
} }

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func ClearCrawlerSessionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := scout.NewClearCrawlerSessionLogic(r.Context(), svcCtx)
data, err := l.ClearCrawlerSession()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func DraftOutreachHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ScoutDraftPathReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := scout.NewDraftOutreachLogic(r.Context(), svcCtx)
data, err := l.DraftOutreach(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func GetActiveBrandHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := scout.NewGetActiveBrandLogic(r.Context(), svcCtx)
data, err := l.GetActiveBrand()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetBrandHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.BrandIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := scout.NewGetBrandLogic(r.Context(), svcCtx)
data, err := l.GetBrand(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetHomeworkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ScoutThemePath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := scout.NewGetHomeworkLogic(r.Context(), svcCtx)
data, err := l.GetHomework(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func GetProductHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ProductIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := scout.NewGetProductLogic(r.Context(), svcCtx)
data, err := l.GetProduct(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ImportProductHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ImportProductReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := scout.NewImportProductLogic(r.Context(), svcCtx)
data, err := l.ImportProduct(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func ListAllProductsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := scout.NewListAllProductsLogic(r.Context(), svcCtx)
data, err := l.ListAllProducts()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func ListBrandsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := scout.NewListBrandsLogic(r.Context(), svcCtx)
data, err := l.ListBrands()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,20 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
)
func ListHomeworkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := scout.NewListHomeworkLogic(r.Context(), svcCtx)
data, err := l.ListHomework()
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ListProductsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ProductListReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := scout.NewListProductsLogic(r.Context(), svcCtx)
data, err := l.ListProducts(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ListScoutPostsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ScoutPostListReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := scout.NewListScoutPostsLogic(r.Context(), svcCtx)
data, err := l.ListScoutPosts(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func MarkPublishedHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ScoutPostIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := scout.NewMarkPublishedLogic(r.Context(), svcCtx)
data, err := l.MarkPublished(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func PrepareBriefHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ScoutBriefReq
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := scout.NewPrepareBriefLogic(r.Context(), svcCtx)
data, err := l.PrepareBrief(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func RemoveBrandHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.BrandIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := scout.NewRemoveBrandLogic(r.Context(), svcCtx)
data, err := l.RemoveBrand(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func RemoveHomeworkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ScoutThemePath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := scout.NewRemoveHomeworkLogic(r.Context(), svcCtx)
data, err := l.RemoveHomework(&req)
response.Write(r.Context(), w, data, err)
}
}

View File

@ -0,0 +1,28 @@
// Code generated by goctl. DO NOT EDIT.
// goctl <no value>
package scout
import (
"net/http"
"apps/backend/internal/logic/scout"
"apps/backend/internal/response"
"apps/backend/internal/svc"
"apps/backend/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func RemoveProductHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ProductIdPath
if err := httpx.Parse(r, &req); err != nil {
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
return
}
l := scout.NewRemoveProductLogic(r.Context(), svcCtx)
data, err := l.RemoveProduct(&req)
response.Write(r.Context(), w, data, err)
}
}

Some files were not shown because too many files have changed in this diff Show More