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。
.PHONY: build test gen-api tidy migrate-up migrate-down
.PHONY: build test test-integration gen-api tidy migrate-up migrate-down
build:
go build -o bin/gateway .
@ -9,6 +9,16 @@ build:
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 勿手寫
gen-api:
goctl api go -api generate/api/gateway.api -dir . -style go_zero --home generate/goctl

View File

@ -1,21 +1,49 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
"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/logx"
)
// Independent worker binary (A-03). M0 stub: config load + heartbeat log only.
// Outbox/job runners land in M2/M4 (T133/T169).
// Workerdemo + threads_token_renew + persona_analyze_* + outbox publish
//
// cd apps/backend && go build -o bin/worker ./cmd/worker
// ./bin/worker -f etc/gateway.yaml
func main() {
configFile := flag.String("f", "etc/gateway.yaml", "config file")
flag.Parse()
@ -25,31 +53,319 @@ func main() {
c.ApplyEnv()
workerID := c.Worker.ID
if workerID == "" {
workerID = "worker-1"
if workerID == "" || workerID == "worker-1" || workerID == "worker-local-1" {
workerID = "demo-worker-1"
}
interval := time.Duration(c.Worker.PollIntervalMs) * time.Millisecond
if interval <= 0 {
interval = 2 * time.Second
}
logx.Infof("haixun worker stub started id=%s interval=%s (no publish yet)", workerID, interval)
fmt.Printf("worker stub running id=%s (Ctrl+C to stop)\n", workerID)
appN := appnotifUC.New(appnotifRepo.NewMonStore(c.Mongo.URI, c.Mongo.Database))
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)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
tick := time.NewTicker(interval)
defer tick.Stop()
ctx := context.Background()
for {
select {
case <-sig:
logx.Infof("worker %s shutting down", workerID)
return
case t := <-tick.C:
// Health heartbeat only — no job/outbox processing in M0.
logx.Infof("worker %s heartbeat at %s", workerID, t.UTC().Format(time.RFC3339))
case <-tick.C:
// 1) claim one due job
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
Host: 0.0.0.0
Port: 8888
Timeout: 30000
# AI 產文/試產可能超過 30s過短會 503 且前端拿不到結果
Timeout: 120000
MaxBytes: 104857600
Log:
@ -32,14 +33,23 @@ Auth:
RefreshSecret: "change-me-refresh-secret"
RefreshExpire: 604800
# 平台預設 key所有會員可用會員在設定頁填的 BYOK 優先)
# 也可用環境變數覆寫PLATFORM_XAI_KEY / PLATFORM_OPENCODE_KEY / PLATFORM_EXA_KEY / PLATFORM_AI_KEY
Platform:
# xAIapi.x.ai— 人設 LLM、文案等
XAIKey: "xai-TtmPWVAz1CdssptRetj9iOFSdSG2LZDffEOTKzIiDmVSDqUL62lVdDladlQIXAwoeHtowWraVqYuHzl1"
# 相容舊名XAIKey 空時會用 AIKey
AIKey: ""
ExaKey: ""
ThreadsAppId: ""
ThreadsAppSecret: ""
# OpenCode Goopencode.ai/zen/go
OpenCodeKey: "sk-sJkJVG2Q3tQEGqgl4cCy10QCVnrHpvuzR8RuE2eTuvruXiVNromsQ7zo6db0Ssj1"
# Exa 網搜
ExaKey: "5d9af9b2-a847-4295-a110-f666049e1073"
ThreadsAppId: "2733369680379930"
ThreadsAppSecret: "31db4afdd8e348d75d76dee8fb9a42d3"
Worker:
ID: "worker-local-1"
# demo-worker 預設 idcmd/worker 若見 worker-local-1 會改成 demo-worker-1
ID: "demo-worker-1"
PollIntervalMs: 2000
Bcrypt:
@ -62,7 +72,7 @@ OAuth:
# 正式 Mailgun SMTP 例:
# Host: smtp.mailgun.org Port: 587
# User: postmaster@YOUR_DOMAIN Password: <API SMTP password>
# DevExposeCode: false
# DevExposeCode: false試產
#
# env 覆寫MAIL_SMTP_* 或 HAIXUN_SMTP_*(舊名相容)
Mail:
@ -77,7 +87,7 @@ Mail:
# 前端 origin重設信連結 + 預設 logo URL/brand-mark.jpg
# 遠端 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
Brand:

View File

@ -39,6 +39,8 @@ func main() {
ctx := svc.NewServiceContext(c)
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())
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 "settings.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,14 +1,29 @@
syntax = "v1"
type (
// GetAi設定 + 模型清單一併回(避免前端分開打兩支)
GetAiReq {
// 可選:預覽另一個 provider 的模型清單(不改存檔)
Provider string `form:"provider,optional"`
}
AiSettingsData {
Provider string `json:"provider"`
Model string `json:"model"`
ResearchProvider string `json:"research_provider"`
ResearchModel string `json:"research_model"`
ApiKeyConfigured bool `json:"api_key_configured"`
ResearchApiKeyConfigured bool `json:"research_api_key_configured"`
PlatformKeyAvailable bool `json:"platform_key_available"`
Provider string `json:"provider"`
Model string `json:"model"`
ResearchProvider string `json:"research_provider"`
ResearchModel string `json:"research_model"`
ApiKeyConfigured bool `json:"api_key_configured"`
ResearchApiKeyConfigured bool `json:"research_api_key_configured"`
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 {
@ -43,8 +58,27 @@ type (
}
ListModelsData {
List []string `json:"list"`
Provider string `json:"provider"`
List []string `json:"list"`
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 {
@handler GetAi
get /ai returns (AiSettingsData)
get /ai (GetAiReq) returns (AiSettingsData)
@handler SaveAi
put /ai (SaveAiReq) returns (AiSettingsData)
@ -68,4 +102,7 @@ service gateway {
@handler ListModels
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
}
Platform struct {
AIKey string `json:",optional"`
// AIKey — legacy alias for XAIKey
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"`
ThreadsAppId string `json:",optional"`
ThreadsAppSecret string `json:",optional"`
@ -62,6 +67,10 @@ type Config struct {
// PublicWebBase — 前端 origin用於重設密碼信內連結logo勿尾斜線
// 例http://127.0.0.1:5173 或 https://threads-tool-dev.30cm.net
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 struct {
Name string `json:",optional,default=Harbor Desk"`
@ -111,9 +120,25 @@ func (c *Config) ApplyEnv() {
if v := os.Getenv("PLATFORM_AI_KEY"); 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 != "" {
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 != "" {
c.Worker.ID = v
}
@ -155,6 +180,9 @@ func (c *Config) ApplyEnv() {
if v := firstEnv("PUBLIC_WEB_BASE", "HAIXUN_PUBLIC_WEB_BASE"); 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 != "" {
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"
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"
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"
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"
threads "apps/backend/internal/handler/threads"
usage "apps/backend/internal/handler/usage"
"apps/backend/internal/svc"
"github.com/zeromicro/go-zero/rest"
@ -171,6 +186,171 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
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(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
@ -185,6 +365,211 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
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(
[]rest.Route{
{
@ -201,6 +586,246 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
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(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthJWT},
@ -230,8 +855,114 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/placement",
Handler: settings.SavePlacementHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/threads",
Handler: settings.GetThreadsHandler(serverCtx),
},
}...,
),
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