fix frontend
This commit is contained in:
parent
c57987f6a8
commit
fcc76911bb
|
|
@ -35,3 +35,6 @@ node_modules/
|
||||||
# --- OS / editor ---
|
# --- OS / editor ---
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.cursor/
|
.cursor/
|
||||||
|
|
||||||
|
# deploy runtime
|
||||||
|
deploy/run/
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
## 封存與主線
|
## 封存與主線
|
||||||
|
|
||||||
- **`old/`**:舊系統整包封存,**只讀參考**。不要在 `old/` 修功能當交付。
|
- **`old/`**:舊系統整包封存,**只讀參考**。不要在 `old/` 修功能當交付。
|
||||||
- **新碼**:只寫在 repo 根下新目錄(`apps/`、`services/` 等,以 `docs/product` 為準)。
|
- **新碼**:只寫在 **`apps/`** 下(前端 `apps/web`、後端 `apps/backend` 等,以 `docs/product` 為準)。
|
||||||
|
- **後端落點**:Phase C 巡樓 API/worker 在 **`apps/backend`**(go-zero;由 `stand-alone-service` 遷入改造)。
|
||||||
|
- **後端寫法(硬性)**:一律 **goctl**:改 `generate/api/*.api` → `make gen-api` → 業務只寫 `internal/logic/**` 與 `internal/module/**`。**禁止**手寫 `handler`/`routes.go` 當主路徑。
|
||||||
- **產品真相**:`docs/product/<slug>/`(requirements → spec → plan → tasks)。
|
- **產品真相**:`docs/product/<slug>/`(requirements → spec → plan → tasks)。
|
||||||
- **流程**:`/spec-driven`(`.grok/skills/spec-driven/`)。
|
- **流程**:`/spec-driven`(`.grok/skills/spec-driven/`)。
|
||||||
|
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -38,6 +38,16 @@ npm run dev # 本機開發,監聽 0.0.0.0:5173(區網可連)
|
||||||
npm run build # 型別檢查 + 生產建置
|
npm run build # 型別檢查 + 生產建置
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 部署(dev 一鍵清乾淨重裝)
|
||||||
|
|
||||||
|
見 [`deploy/README.md`](./deploy/README.md)。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp deploy/.env.example deploy/.env.dev # 首次:填密鑰
|
||||||
|
./deploy/scripts/redeploy.sh --yes --kill-orphan
|
||||||
|
# 可選 nginx:./deploy/scripts/install-nginx.sh
|
||||||
|
```
|
||||||
|
|
||||||
產品任務進度:`docs/product/haixun-console/tasks/INDEX.md`(目前從 T001 起)。
|
產品任務進度:`docs/product/haixun-console/tasks/INDEX.md`(目前從 T001 起)。
|
||||||
|
|
||||||
## 開發流程
|
## 開發流程
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
/bin/
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
coverage.out
|
||||||
|
.data/
|
||||||
|
uploads/
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# 遷入說明(精簡)
|
||||||
|
|
||||||
|
- 骨架參考 `stand-alone-service`(go-zero module 分層、lib/mongo 精神)。
|
||||||
|
- **不**整包 rsync 電商與 `code.30cm.net` 私有依賴。
|
||||||
|
- API:`generate/api` → goctl。
|
||||||
|
- DB:`generate/database/mongo` → golang-migrate。
|
||||||
|
- 執行體:`gateway` + `cmd/worker` only。
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# 只保留會重複打的指令;其餘直接 go / migrate。
|
||||||
|
|
||||||
|
.PHONY: build test gen-api tidy migrate-up migrate-down
|
||||||
|
|
||||||
|
build:
|
||||||
|
go build -o bin/gateway .
|
||||||
|
go build -o bin/worker ./cmd/worker
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
# 改 generate/api/*.api 後執行;handler 勿手寫
|
||||||
|
gen-api:
|
||||||
|
goctl api go -api generate/api/gateway.api -dir . -style go_zero --home generate/goctl
|
||||||
|
|
||||||
|
tidy:
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
# DB:檔案在 generate/database/mongo;需本機已裝 migrate(mongodb tag)
|
||||||
|
# 例:MONGO_URL=mongodb://127.0.0.1:27017/haixun
|
||||||
|
MONGO_URL ?= mongodb://127.0.0.1:27017/haixun
|
||||||
|
|
||||||
|
migrate-up:
|
||||||
|
migrate -path generate/database/mongo -database "$(MONGO_URL)" up
|
||||||
|
|
||||||
|
migrate-down:
|
||||||
|
migrate -path generate/database/mongo -database "$(MONGO_URL)" down 1
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
# apps/backend
|
||||||
|
|
||||||
|
Harbor Desk Phase C(go-zero)。
|
||||||
|
|
||||||
|
## 原則
|
||||||
|
|
||||||
|
| 做 | 不做 |
|
||||||
|
|----|------|
|
||||||
|
| `generate/api` + `make gen-api` | 手寫 handler/routes |
|
||||||
|
| `generate/database/mongo` + `make migrate-up` | `cmd/init`、`cmd/seeder` 程式遷移 |
|
||||||
|
| `monc` + `CacheRedis` | 自己半套 mongo cache |
|
||||||
|
| `internal/logic` + `module/*/domain|repository|usecase` | 業務塞進 handler |
|
||||||
|
|
||||||
|
## 指令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/backend
|
||||||
|
make build # gateway + worker
|
||||||
|
make test
|
||||||
|
make gen-api # 改 .api 後
|
||||||
|
make migrate-up # 改 generate/database 後
|
||||||
|
make tidy
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 需要 Mongo + Redis(go-zero monc / redis)
|
||||||
|
# Mongo 若有帳密(本機 infra 常見),一定要帶進 URI:
|
||||||
|
export MONGO_URI='mongodb://USER:PASS@127.0.0.1:27017/?authSource=admin'
|
||||||
|
export MONGO_URL="$MONGO_URI" # migrate 用
|
||||||
|
make migrate-up
|
||||||
|
go run . -f etc/gateway.yaml
|
||||||
|
# admin@haixun.local / admin123(seed,uid=1000000 → 顯示 01000000)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 日誌與密碼(安全)
|
||||||
|
|
||||||
|
| 禁止出現在 log | 說明 |
|
||||||
|
|----------------|------|
|
||||||
|
| `password` / `new_password` / `current_password` / `temporary_password` | 明碼 |
|
||||||
|
| `password_hash` | bcrypt 也不可打 log |
|
||||||
|
| access/refresh token、api_key | 憑證 |
|
||||||
|
|
||||||
|
實作:
|
||||||
|
|
||||||
|
- 啟動時 `securelog.SilenceMongo()` → 關閉 go-zero **mon** 文件 dump(否則 ReplaceOne 會印出整份 member)
|
||||||
|
- `RestConf.Verbose = false` → 不印 request body
|
||||||
|
- 自寫 log 請用 `securelog.RedactAny(v)` / 勿 `logx.Infof("%+v", req)`
|
||||||
|
|
||||||
|
HTTP access log 只記 method/path/status/duration,不含 body。
|
||||||
|
|
||||||
|
### 物件儲存(頭像)— **僅 MinIO/S3**
|
||||||
|
|
||||||
|
不落本機磁碟(無 localstore / `/files`)。未設定 Endpoint 時 upload 直接失敗。
|
||||||
|
|
||||||
|
| 項目 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| API | `POST /api/v1/media/upload`(需 JWT)`{ content: dataURL, kind: "avatar" }` |
|
||||||
|
| 回傳 | `{ url, key }` — Mongo member **只存 url** |
|
||||||
|
| 後端 | MinIO(dev)或 S3(prod),S3 API + path-style |
|
||||||
|
| env | `HAIXUN_STORAGE_S3_*` 或 `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` |
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ObjectStorage:
|
||||||
|
Endpoint: "http://127.0.0.1:9000" # 必填
|
||||||
|
Bucket: "haixun-assets"
|
||||||
|
AccessKey: "haixun"
|
||||||
|
SecretKey: "..."
|
||||||
|
UsePathStyle: true
|
||||||
|
PublicBaseURL: "" # 或 CDN / 反向代理公開前綴
|
||||||
|
```
|
||||||
|
|
||||||
|
FE:選頭像 → upload → URL → `PATCH profile`(禁止 data URL)。
|
||||||
|
|
||||||
|
### 密碼政策(前後端一致)
|
||||||
|
|
||||||
|
| 規則 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| 長度 | **≥ 12** 字元 |
|
||||||
|
| 組成 | **大寫 + 小寫 + 數字 + 符號** 皆須至少一個 |
|
||||||
|
| 後端 | `ValidatePasswordPolicy` → 不符回 **400003** + `domain.PasswordPolicyEN` |
|
||||||
|
| 前端 | `passwordPolicy.ts` + 語言包 `password.policy.hint` / `api.err.400003` |
|
||||||
|
|
||||||
|
**同一句說明:**
|
||||||
|
|
||||||
|
- EN: `Password must be at least 12 characters with upper, lower, digit, and symbol`
|
||||||
|
- ZH: `密碼須至少 12 碼,且含大寫、小寫、數字與符號`
|
||||||
|
|
||||||
|
前端依 code 400003 顯示語系文案;placeholder/hint/錯誤都用同一 key,勿另寫一句。
|
||||||
|
|
||||||
|
### 寄信(信箱驗證 / 重設密碼)
|
||||||
|
|
||||||
|
業務在 `internal/logic/auth/*`:產碼 → `Notification.RenderVerify` → `SendEmail`。
|
||||||
|
寄送實作在 `internal/module/notification`(SMTP,Mailgun 相容)。
|
||||||
|
|
||||||
|
#### 模板與多語
|
||||||
|
|
||||||
|
- 引擎與 stand-alone 相同:**hermes**(`github.com/matcornic/hermes/v2`)
|
||||||
|
- 語系:`zh-TW`(預設)/`en`;來源為會員 `preferred_language`,忘記密碼則用請求 `language`(FE 當前 locale)
|
||||||
|
- Logo:`Brand.LogoURL` 或預設 `{PublicWebBase}/brand-mark.jpg`(`apps/web/public/brand-mark.jpg`)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
PublicWebBase: "http://127.0.0.1:5173"
|
||||||
|
Brand:
|
||||||
|
Name: "Harbor Desk"
|
||||||
|
LogoURL: "" # 空 = PublicWebBase/brand-mark.jpg
|
||||||
|
Copyright: "© Harbor Desk"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 本機:Mailpit(推薦)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 已含在 old/infra docker-compose(mongo/redis/mailpit)
|
||||||
|
cd old/infra && docker compose --env-file ../deploy/.env.dev up -d mailpit
|
||||||
|
|
||||||
|
# UI 看信
|
||||||
|
open http://127.0.0.1:8025
|
||||||
|
```
|
||||||
|
|
||||||
|
| 項目 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| SMTP | `127.0.0.1:1025`(無密碼也可;可開 AUTH accept-any 模擬 Mailgun) |
|
||||||
|
| Web UI | http://127.0.0.1:8025 |
|
||||||
|
| gateway 預設 | `Mail.SMTP.Host=127.0.0.1` `Port=1025` |
|
||||||
|
|
||||||
|
呼叫 `POST /api/v1/auth/email/send-code` 或 `password/request-reset` 後,到 Mailpit UI 看 HTML 信。
|
||||||
|
#### 正式:Mailgun SMTP
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Mail:
|
||||||
|
Sender: "Harbor Desk <noreply@your-domain.com>"
|
||||||
|
SMTP:
|
||||||
|
Host: "smtp.mailgun.org"
|
||||||
|
Port: 587
|
||||||
|
User: "postmaster@YOUR_DOMAIN"
|
||||||
|
Password: "<Mailgun SMTP password>"
|
||||||
|
DevExposeCode: false
|
||||||
|
```
|
||||||
|
|
||||||
|
同一套 `Mail.SMTP` 設定;本機指 Mailpit、上線指 Mailgun,**code 不用改**。
|
||||||
|
|
||||||
|
env 覆寫:`MAIL_SMTP_*` 或 `HAIXUN_SMTP_*`(與 `old/deploy/.env.dev` 相容)。
|
||||||
|
|
||||||
|
| SMTP Host | 行為 |
|
||||||
|
|-----------|------|
|
||||||
|
| **空** | log-only,不真寄 |
|
||||||
|
| **Mailpit / Mailgun** | 真走 SMTP 投遞 |
|
||||||
|
|
||||||
|
### 前後端串不起來?檢查這幾項
|
||||||
|
|
||||||
|
1. **gateway 有在跑** `ss -tlnp | grep 8888`;沒有就 `go run . -f etc/gateway.yaml`
|
||||||
|
2. **Mongo URI 有帳密**(`export MONGO_URI=...`;沒帶會查不到 seed)
|
||||||
|
3. **Redis 有密碼**(`export REDIS_PASSWORD=...`;沒帶 monc 回 `NOAUTH` → API 500)
|
||||||
|
4. **已跑 migrate**(`make migrate-up`,seed `admin@haixun.local`)
|
||||||
|
5. **FE 切到 live**:設定 → 資料來源 → Live(預設是 **mock**,根本不會打後端)
|
||||||
|
6. **API base**:`apps/web/.env` 建議 `VITE_API_BASE=`(空),走 Vite proxy `/api` → `:8888`
|
||||||
|
遠端開 `*.30cm.net` 時**不要**寫死 `http://127.0.0.1:8888`(那會打到使用者自己電腦)
|
||||||
|
7. 切 live 後**重新登入** seed:`admin@haixun.local` / `admin123`
|
||||||
|
|
||||||
|
| 資料 | 存哪 | 關機 |
|
||||||
|
|------|------|------|
|
||||||
|
| 會員 / settings | **Mongo**(monc) | 保留 |
|
||||||
|
| refresh token / 驗證碼 | **Mongo**(TTL index) | 保留(過期自動清) |
|
||||||
|
| monc 實體快取 | Redis(CacheRedis) | 可丟,會從 Mongo 回填 |
|
||||||
|
|
||||||
|
## 目錄
|
||||||
|
|
||||||
|
```text
|
||||||
|
generate/api/ # goctl 契約
|
||||||
|
generate/database/mongo # golang-migrate JSON
|
||||||
|
generate/goctl/ # handler 模板(102000)
|
||||||
|
cmd/worker/ # 獨立 worker
|
||||||
|
internal/handler|logic # goctl 產 / 薄邏輯
|
||||||
|
internal/module/ # domain · repository · usecase
|
||||||
|
internal/lib/mongo # monc URI 小工具
|
||||||
|
etc/gateway.yaml # 唯一設定
|
||||||
|
```
|
||||||
|
|
||||||
|
## 設定摘要
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
CacheRedis: # monc Redis 快取
|
||||||
|
- Host: 127.0.0.1:6379
|
||||||
|
Type: node
|
||||||
|
Mongo:
|
||||||
|
URI: "mongodb://127.0.0.1:27017" # 有 auth 用 MONGO_URI 覆寫
|
||||||
|
Database: "haixun"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 會員 uid
|
||||||
|
|
||||||
|
- 從 **1_000_000** 起(seed admin = `1000000`)
|
||||||
|
- 顯示補成 **8 位**:`01000000`(`%08d` / FE `padStart(8,"0")`)
|
||||||
|
- 新註冊:`1000001`, `1000002`, …
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"apps/backend/internal/config"
|
||||||
|
|
||||||
|
"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).
|
||||||
|
func main() {
|
||||||
|
configFile := flag.String("f", "etc/gateway.yaml", "config file")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var c config.Config
|
||||||
|
conf.MustLoad(*configFile, &c)
|
||||||
|
c.ApplyEnv()
|
||||||
|
|
||||||
|
workerID := c.Worker.ID
|
||||||
|
if workerID == "" {
|
||||||
|
workerID = "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)
|
||||||
|
|
||||||
|
sig := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
tick := time.NewTicker(interval)
|
||||||
|
defer tick.Stop()
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
# 巡樓 gateway — 唯一主設定檔(A-02)
|
||||||
|
# Secret 請用環境變數覆寫。
|
||||||
|
Name: haixun-gateway
|
||||||
|
Host: 0.0.0.0
|
||||||
|
Port: 8888
|
||||||
|
Timeout: 30000
|
||||||
|
MaxBytes: 104857600
|
||||||
|
|
||||||
|
Log:
|
||||||
|
Mode: console
|
||||||
|
Level: info
|
||||||
|
|
||||||
|
# go-zero monc 快取(Redis)— 與 monc.MustNewModel / CacheRedis 對接
|
||||||
|
# 若 Redis 有 requirepass:設 Pass 或環境變數 REDIS_PASSWORD / HAIXUN_REDIS_PASSWORD
|
||||||
|
# (沒帶密碼會 NOAUTH,登入 API 回 500)
|
||||||
|
CacheRedis:
|
||||||
|
- Host: 127.0.0.1:6379
|
||||||
|
Type: node
|
||||||
|
Pass: ""
|
||||||
|
|
||||||
|
# Mongo — mon/monc 使用完整 URI(含帳密時寫在 URI;可用環境變數 MONGO_URI 覆寫)
|
||||||
|
# 本機有 auth 的例子:
|
||||||
|
# export MONGO_URI='mongodb://USER:PASS@127.0.0.1:27017/?authSource=admin'
|
||||||
|
# 沒設帳密但 Mongo 開了 auth → 查詢會失敗,登入會 500 而不是「密碼錯」
|
||||||
|
Mongo:
|
||||||
|
URI: "mongodb://127.0.0.1:27017"
|
||||||
|
Database: "haixun"
|
||||||
|
|
||||||
|
Auth:
|
||||||
|
AccessSecret: "change-me-access-secret"
|
||||||
|
AccessExpire: 86400
|
||||||
|
RefreshSecret: "change-me-refresh-secret"
|
||||||
|
RefreshExpire: 604800
|
||||||
|
|
||||||
|
Platform:
|
||||||
|
AIKey: ""
|
||||||
|
ExaKey: ""
|
||||||
|
ThreadsAppId: ""
|
||||||
|
ThreadsAppSecret: ""
|
||||||
|
|
||||||
|
Worker:
|
||||||
|
ID: "worker-local-1"
|
||||||
|
PollIntervalMs: 2000
|
||||||
|
|
||||||
|
Bcrypt:
|
||||||
|
Cost: 10
|
||||||
|
|
||||||
|
# OAuth(空則 callback 走 mock / 開發路徑)
|
||||||
|
OAuth:
|
||||||
|
GoogleClientID: ""
|
||||||
|
LineClientID: ""
|
||||||
|
LineClientSecret: ""
|
||||||
|
LineRedirectURI: ""
|
||||||
|
|
||||||
|
# 驗證碼/重設密碼寄信
|
||||||
|
#
|
||||||
|
# 本機(Mailpit,Mailgun 相容 SMTP 介面):
|
||||||
|
# Host: 127.0.0.1 Port: 1025 User/Password 可空
|
||||||
|
# UI: http://127.0.0.1:8025
|
||||||
|
# 容器:old/infra docker-compose 的 mailpit 服務
|
||||||
|
#
|
||||||
|
# 正式 Mailgun SMTP 例:
|
||||||
|
# Host: smtp.mailgun.org Port: 587
|
||||||
|
# User: postmaster@YOUR_DOMAIN Password: <API SMTP password>
|
||||||
|
# DevExposeCode: false
|
||||||
|
#
|
||||||
|
# env 覆寫:MAIL_SMTP_* 或 HAIXUN_SMTP_*(舊名相容)
|
||||||
|
Mail:
|
||||||
|
Sender: "Harbor Desk <noreply@haixun.local>"
|
||||||
|
SMTP:
|
||||||
|
Host: "127.0.0.1"
|
||||||
|
Port: 1025
|
||||||
|
User: ""
|
||||||
|
Password: ""
|
||||||
|
# 不再於 API 回傳驗證碼;碼只在郵件(Mailpit/SMTP)
|
||||||
|
DevExposeCode: false
|
||||||
|
|
||||||
|
# 前端 origin:重設信連結 + 預設 logo URL(/brand-mark.jpg)
|
||||||
|
# 遠端 dev:https://threads-tool-dev.30cm.net
|
||||||
|
PublicWebBase: "http://127.0.0.1:5173"
|
||||||
|
|
||||||
|
# 郵件品牌(hermes 模板;LogoURL 空 = PublicWebBase/brand-mark.jpg)
|
||||||
|
Brand:
|
||||||
|
Name: "Harbor Desk"
|
||||||
|
LogoURL: ""
|
||||||
|
Copyright: "© Harbor Desk"
|
||||||
|
|
||||||
|
# 物件儲存(頭像等)— **只接 MinIO/S3**
|
||||||
|
#
|
||||||
|
# Endpoint = gateway 上傳用(本機 MinIO,一定要 :9000)
|
||||||
|
# PublicBaseURL = 瀏覽器讀圖用(走 nginx :80,不要寫 port、不要加 /bucket)
|
||||||
|
# 成品網址 = PublicBaseURL + "/" + key
|
||||||
|
# 例:http://10.0.0.5/haixun-assets/avatar/1000000/xxx.png
|
||||||
|
#
|
||||||
|
# Secret 建議用 env,不要 commit:
|
||||||
|
# export HAIXUN_STORAGE_S3_ACCESS_KEY=haixun
|
||||||
|
# export HAIXUN_STORAGE_S3_SECRET_KEY='你的密碼'
|
||||||
|
# 或 MINIO_ROOT_USER / MINIO_ROOT_PASSWORD(ApplyEnv 會讀)
|
||||||
|
ObjectStorage:
|
||||||
|
Endpoint: "http://127.0.0.1:9000"
|
||||||
|
Region: "us-east-1"
|
||||||
|
Bucket: "haixun-assets"
|
||||||
|
AccessKey: "haixun"
|
||||||
|
SecretKey: ""
|
||||||
|
UsePathStyle: true
|
||||||
|
# env HAIXUN_STORAGE_S3_PUBLIC_BASE_URL 會覆寫;勿用 /api/v1/public/assets(新後端無此 route)
|
||||||
|
PublicBaseURL: "https://threads-tool-dev.30cm.net/haixun-assets"
|
||||||
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"apps/backend/internal/config"
|
||||||
|
"apps/backend/internal/handler"
|
||||||
|
"apps/backend/internal/lib/securelog"
|
||||||
|
"apps/backend/internal/middleware"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/conf"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"github.com/zeromicro/go-zero/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gateway entry (go-zero). API from goctl; DB schema from generate/database migrate.
|
||||||
|
func main() {
|
||||||
|
// 必須在 mon/monc 建立前呼叫:否則 Mongo ReplaceOne 會把 password_hash 整包打進 log
|
||||||
|
securelog.SilenceMongo()
|
||||||
|
|
||||||
|
configFile := flag.String("f", "etc/gateway.yaml", "config file")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var c config.Config
|
||||||
|
conf.MustLoad(*configFile, &c)
|
||||||
|
c.ApplyEnv()
|
||||||
|
|
||||||
|
// 禁止 Verbose(否則可能印 request body 含 password)
|
||||||
|
c.RestConf.Verbose = false
|
||||||
|
|
||||||
|
server := rest.MustNewServer(c.RestConf,
|
||||||
|
rest.WithNotAllowedHandler(middleware.NewCorsMiddleware().Handler()),
|
||||||
|
)
|
||||||
|
defer server.Stop()
|
||||||
|
server.Use(middleware.NewCorsMiddleware().Handle)
|
||||||
|
|
||||||
|
ctx := svc.NewServiceContext(c)
|
||||||
|
handler.RegisterHandlers(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)
|
||||||
|
server.Start()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
type (
|
||||||
|
AdminListMembersReq {
|
||||||
|
Page int `form:"page,optional"`
|
||||||
|
PageSize int `form:"pageSize,optional"`
|
||||||
|
Query string `form:"query,optional"`
|
||||||
|
Status string `form:"status,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminUidPath {
|
||||||
|
Uid string `path:"uid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminCreateMemberReq {
|
||||||
|
DisplayName string `json:"display_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Phone string `json:"phone,optional"`
|
||||||
|
Password string `json:"password,optional"`
|
||||||
|
Roles []string `json:"roles,optional"`
|
||||||
|
EmailVerified bool `json:"email_verified,optional"`
|
||||||
|
Bio string `json:"bio,optional"`
|
||||||
|
Nickname string `json:"nickname,optional"`
|
||||||
|
FullName string `json:"full_name,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminCreateMemberData {
|
||||||
|
User MemberPublic `json:"user"`
|
||||||
|
TemporaryPassword string `json:"temporary_password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminSuspendReq {
|
||||||
|
Uid string `path:"uid"`
|
||||||
|
Suspended bool `json:"suspended"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminRolesReq {
|
||||||
|
Uid string `path:"uid"`
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminEmailVerifiedReq {
|
||||||
|
Uid string `path:"uid"`
|
||||||
|
Verified bool `json:"verified"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminResetPasswordReq {
|
||||||
|
Uid string `path:"uid"`
|
||||||
|
NewPassword string `json:"new_password,optional"`
|
||||||
|
Password string `json:"password,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminStatusReq {
|
||||||
|
Uid string `path:"uid"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminListData {
|
||||||
|
List []MemberPublic `json:"list"`
|
||||||
|
Pagination Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminMemberIdentitiesData {
|
||||||
|
List []IdentityPublic `json:"list"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: admin
|
||||||
|
prefix: /api/v1/admin
|
||||||
|
middleware: AuthJWT,AdminAuth
|
||||||
|
)
|
||||||
|
service gateway {
|
||||||
|
@handler ListMembers
|
||||||
|
get /members (AdminListMembersReq) returns (AdminListData)
|
||||||
|
|
||||||
|
@handler CreateMember
|
||||||
|
post /members (AdminCreateMemberReq) returns (AdminCreateMemberData)
|
||||||
|
|
||||||
|
@handler GetMember
|
||||||
|
get /members/:uid (AdminUidPath) returns (MemberPublic)
|
||||||
|
|
||||||
|
@handler SetSuspended
|
||||||
|
post /members/:uid/suspend (AdminSuspendReq) returns (MemberPublic)
|
||||||
|
|
||||||
|
@handler SetRoles
|
||||||
|
post /members/:uid/roles (AdminRolesReq) returns (MemberPublic)
|
||||||
|
|
||||||
|
@handler SetEmailVerified
|
||||||
|
post /members/:uid/email-verified (AdminEmailVerifiedReq) returns (MemberPublic)
|
||||||
|
|
||||||
|
@handler AdminResetPassword
|
||||||
|
post /members/:uid/reset-password (AdminResetPasswordReq) returns (AdminCreateMemberData)
|
||||||
|
|
||||||
|
@handler SetStatus
|
||||||
|
post /members/:uid/status (AdminStatusReq) returns (MemberPublic)
|
||||||
|
|
||||||
|
@handler ListMemberIdentities
|
||||||
|
get /members/:uid/identities (AdminUidPath) returns (AdminMemberIdentitiesData)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
type (
|
||||||
|
AuthLoginReq {
|
||||||
|
Email string `json:"email,optional"`
|
||||||
|
LoginId string `json:"login_id,optional"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Platform string `json:"platform,optional"` // platform|google|line (default platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthRegisterReq {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
DisplayName string `json:"display_name,optional"`
|
||||||
|
Phone string `json:"phone,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthRefreshReq {
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthLogoutReq {
|
||||||
|
RefreshToken string `json:"refresh_token,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenPair {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
ExpiresIn int64 `json:"expires_in"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthSessionData {
|
||||||
|
Tokens TokenPair `json:"tokens"`
|
||||||
|
Member MemberPublic `json:"member"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthTokensOnly {
|
||||||
|
Tokens TokenPair `json:"tokens"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthProfilePatchReq {
|
||||||
|
DisplayName string `json:"display_name,optional"`
|
||||||
|
Nickname string `json:"nickname,optional"`
|
||||||
|
FullName string `json:"full_name,optional"`
|
||||||
|
Email string `json:"email,optional"`
|
||||||
|
Phone string `json:"phone,optional"`
|
||||||
|
Bio string `json:"bio,optional"`
|
||||||
|
Timezone string `json:"timezone,optional"`
|
||||||
|
NotifyEmail *bool `json:"notify_email,optional"`
|
||||||
|
// *string:省略=不改;""=清除頭像;http(s)=設定(禁止 data URL)
|
||||||
|
AvatarUrl *string `json:"avatar_url,optional"`
|
||||||
|
GenderCode *int `json:"gender_code,optional"`
|
||||||
|
Birthdate *int64 `json:"birthdate,optional"`
|
||||||
|
National string `json:"national,optional"`
|
||||||
|
Address string `json:"address,optional"`
|
||||||
|
PostCode string `json:"post_code,optional"`
|
||||||
|
PreferredLanguage string `json:"preferred_language,optional"`
|
||||||
|
Currency string `json:"currency,optional"`
|
||||||
|
CurrentPassword string `json:"current_password,optional"`
|
||||||
|
NewPassword string `json:"new_password,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthRequestResetReq {
|
||||||
|
Email string `json:"email"`
|
||||||
|
// FE locale: zh-TW | en | en-US(未登入時用;有會員則優先 preferred_language)
|
||||||
|
Language string `json:"language,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthRequestResetData {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
MockCode string `json:"mock_code,optional"`
|
||||||
|
MockResetPath string `json:"mock_reset_path,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthResetPasswordReq {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Token string `json:"token,optional"`
|
||||||
|
Code string `json:"code,optional"`
|
||||||
|
NewPassword string `json:"new_password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthSendCodeData {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
MockCode string `json:"mock_code,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthVerifyEmailReq {
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthOAuthProviderPath {
|
||||||
|
Provider string `path:"provider"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthOAuthCallbackReq {
|
||||||
|
Provider string `path:"provider"`
|
||||||
|
Code string `json:"code,optional"`
|
||||||
|
IdToken string `json:"id_token,optional"`
|
||||||
|
AccessToken string `json:"access_token,optional"`
|
||||||
|
RedirectUri string `json:"redirect_uri,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthOAuthStartData {
|
||||||
|
AuthorizeUrl string `json:"authorize_url"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
State string `json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthBindReq {
|
||||||
|
LoginId string `json:"login_id"`
|
||||||
|
Platform string `json:"platform"`
|
||||||
|
AccountType string `json:"account_type,optional"`
|
||||||
|
Password string `json:"password,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthUnbindReq {
|
||||||
|
LoginId string `json:"login_id"`
|
||||||
|
Platform string `json:"platform"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthIdentitiesData {
|
||||||
|
List []IdentityPublic `json:"list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthLogoutAllData {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: auth
|
||||||
|
prefix: /api/v1/auth
|
||||||
|
)
|
||||||
|
service gateway {
|
||||||
|
@handler Login
|
||||||
|
post /login (AuthLoginReq) returns (AuthSessionData)
|
||||||
|
|
||||||
|
@handler Logout
|
||||||
|
post /logout (AuthLogoutReq) returns (OkData)
|
||||||
|
|
||||||
|
@handler Refresh
|
||||||
|
post /refresh (AuthRefreshReq) returns (AuthTokensOnly)
|
||||||
|
|
||||||
|
@handler Register
|
||||||
|
post /register (AuthRegisterReq) returns (AuthSessionData)
|
||||||
|
|
||||||
|
@handler RequestPasswordReset
|
||||||
|
post /password/request-reset (AuthRequestResetReq) returns (AuthRequestResetData)
|
||||||
|
|
||||||
|
@handler ResetPassword
|
||||||
|
post /password/reset (AuthResetPasswordReq) returns (OkData)
|
||||||
|
|
||||||
|
@handler OAuthStart
|
||||||
|
post /oauth/:provider/start (AuthOAuthProviderPath) returns (AuthOAuthStartData)
|
||||||
|
|
||||||
|
@handler OAuthCallback
|
||||||
|
post /oauth/:provider/callback (AuthOAuthCallbackReq) returns (AuthSessionData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: auth
|
||||||
|
prefix: /api/v1/auth
|
||||||
|
middleware: AuthJWT
|
||||||
|
)
|
||||||
|
service gateway {
|
||||||
|
@handler Me
|
||||||
|
get /me returns (MemberPublic)
|
||||||
|
|
||||||
|
@handler UpdateProfile
|
||||||
|
patch /profile (AuthProfilePatchReq) returns (MemberPublic)
|
||||||
|
|
||||||
|
@handler UpdateProfilePut
|
||||||
|
put /profile (AuthProfilePatchReq) returns (MemberPublic)
|
||||||
|
|
||||||
|
@handler SendEmailCode
|
||||||
|
post /email/send-code returns (AuthSendCodeData)
|
||||||
|
|
||||||
|
@handler VerifyEmail
|
||||||
|
post /email/verify (AuthVerifyEmailReq) returns (MemberPublic)
|
||||||
|
|
||||||
|
@handler ListIdentities
|
||||||
|
get /identities returns (AuthIdentitiesData)
|
||||||
|
|
||||||
|
@handler BindAccount
|
||||||
|
post /bind (AuthBindReq) returns (IdentityPublic)
|
||||||
|
|
||||||
|
@handler UnbindAccount
|
||||||
|
post /unbind (AuthUnbindReq) returns (OkData)
|
||||||
|
|
||||||
|
@handler LogoutAll
|
||||||
|
post /logout-all returns (AuthLogoutAllData)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
type Empty {}
|
||||||
|
|
||||||
|
type OkData {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Message string `json:"message,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pagination {
|
||||||
|
Page int `json:"page"`
|
||||||
|
PageSize int `json:"pageSize"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
TotalPages int `json:"totalPages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdentityPublic — login channel bound to member
|
||||||
|
type IdentityPublic {
|
||||||
|
LoginId string `json:"login_id"`
|
||||||
|
Platform string `json:"platform"`
|
||||||
|
AccountType string `json:"account_type"`
|
||||||
|
CreatedAt int64 `json:"created_at,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemberPublic — full profile (stand-alone user_info + Harbor Desk fields)
|
||||||
|
type MemberPublic {
|
||||||
|
TenantId string `json:"tenant_id"`
|
||||||
|
Uid int64 `json:"uid"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Phone string `json:"phone,optional"`
|
||||||
|
DisplayName string `json:"display_name"`
|
||||||
|
Nickname string `json:"nickname,optional"`
|
||||||
|
FullName string `json:"full_name,optional"`
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
EmailVerifiedAt int64 `json:"email_verified_at,optional"`
|
||||||
|
PhoneVerified bool `json:"phone_verified,optional"`
|
||||||
|
PhoneVerifiedAt int64 `json:"phone_verified_at,optional"`
|
||||||
|
InviteCode string `json:"invite_code,optional"`
|
||||||
|
ParentUid int64 `json:"parent_uid,optional"`
|
||||||
|
Bio string `json:"bio,optional"`
|
||||||
|
Timezone string `json:"timezone,optional"`
|
||||||
|
AvatarUrl string `json:"avatar_url,optional"`
|
||||||
|
NotifyEmail bool `json:"notify_email,optional"`
|
||||||
|
GenderCode int `json:"gender_code,optional"`
|
||||||
|
Birthdate int64 `json:"birthdate,optional"`
|
||||||
|
National string `json:"national,optional"`
|
||||||
|
Address string `json:"address,optional"`
|
||||||
|
PostCode string `json:"post_code,optional"`
|
||||||
|
PreferredLanguage string `json:"preferred_language,optional"`
|
||||||
|
Currency string `json:"currency,optional"`
|
||||||
|
Identities []IdentityPublic `json:"identities,optional"`
|
||||||
|
JoinedAt int64 `json:"joined_at,optional"`
|
||||||
|
CreatedAt int64 `json:"created_at,optional"`
|
||||||
|
UpdatedAt int64 `json:"updated_at,optional"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
info (
|
||||||
|
title: "haixun-gateway"
|
||||||
|
desc: "Harbor Desk Phase C live API (goctl)"
|
||||||
|
author: "haixun"
|
||||||
|
version: "0.2.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
import "common.api"
|
||||||
|
import "ping.api"
|
||||||
|
import "auth.api"
|
||||||
|
import "admin.api"
|
||||||
|
import "settings.api"
|
||||||
|
import "media.api"
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
type (
|
||||||
|
MediaUploadReq {
|
||||||
|
// content: data URL (data:image/jpeg;base64,...) 或純 base64
|
||||||
|
Content string `json:"content"`
|
||||||
|
// kind: avatar | other(決定 object path 前綴)
|
||||||
|
Kind string `json:"kind,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaUploadData {
|
||||||
|
Url string `json:"url"`
|
||||||
|
Key string `json:"key,optional"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: media
|
||||||
|
prefix: /api/v1/media
|
||||||
|
middleware: AuthJWT
|
||||||
|
)
|
||||||
|
service gateway {
|
||||||
|
@handler Upload
|
||||||
|
post /upload (MediaUploadReq) returns (MediaUploadData)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
type HealthData {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Service string `json:"service"`
|
||||||
|
}
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: ping
|
||||||
|
prefix: /api/v1
|
||||||
|
)
|
||||||
|
service gateway {
|
||||||
|
@handler Ping
|
||||||
|
get /ping returns (HealthData)
|
||||||
|
|
||||||
|
@handler Health
|
||||||
|
get /health returns (HealthData)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
type (
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveAiReq {
|
||||||
|
Provider string `json:"provider,optional"`
|
||||||
|
Model string `json:"model,optional"`
|
||||||
|
ResearchProvider string `json:"research_provider,optional"`
|
||||||
|
ResearchModel string `json:"research_model,optional"`
|
||||||
|
ApiKey string `json:"api_key,optional"`
|
||||||
|
ResearchApiKey string `json:"research_api_key,optional"`
|
||||||
|
ApiKeyConfigured *bool `json:"api_key_configured,optional"`
|
||||||
|
ResearchApiKeyConfigured *bool `json:"research_api_key_configured,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
PlacementSettingsData {
|
||||||
|
WebSearchProvider string `json:"web_search_provider"`
|
||||||
|
ExpandStrategy string `json:"expand_strategy"`
|
||||||
|
BraveApiKeyConfigured bool `json:"brave_api_key_configured"`
|
||||||
|
ExaApiKeyConfigured bool `json:"exa_api_key_configured"`
|
||||||
|
DevModeEnabled bool `json:"dev_mode_enabled"`
|
||||||
|
PlatformKeyAvailable bool `json:"platform_key_available"`
|
||||||
|
}
|
||||||
|
|
||||||
|
SavePlacementReq {
|
||||||
|
ExpandStrategy string `json:"expand_strategy,optional"`
|
||||||
|
DevModeEnabled *bool `json:"dev_mode_enabled,optional"`
|
||||||
|
ExaApiKey string `json:"exa_api_key,optional"`
|
||||||
|
ExaApiKeyConfigured *bool `json:"exa_api_key_configured,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
ListModelsReq {
|
||||||
|
Provider string `form:"provider,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
ListModelsData {
|
||||||
|
List []string `json:"list"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@server (
|
||||||
|
group: settings
|
||||||
|
prefix: /api/v1/settings
|
||||||
|
middleware: AuthJWT
|
||||||
|
)
|
||||||
|
service gateway {
|
||||||
|
@handler GetAi
|
||||||
|
get /ai returns (AiSettingsData)
|
||||||
|
|
||||||
|
@handler SaveAi
|
||||||
|
put /ai (SaveAiReq) returns (AiSettingsData)
|
||||||
|
|
||||||
|
@handler GetPlacement
|
||||||
|
get /placement returns (PlacementSettingsData)
|
||||||
|
|
||||||
|
@handler SavePlacement
|
||||||
|
put /placement (SavePlacementReq) returns (PlacementSettingsData)
|
||||||
|
|
||||||
|
@handler ListModels
|
||||||
|
get /models (ListModelsReq) returns (ListModelsData)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Database migrations(golang-migrate + MongoDB)
|
||||||
|
|
||||||
|
**第一性原理:** index/seed 用 **檔案** 宣告,交給 `migrate`;沒有 Go seeder/init 程式。
|
||||||
|
|
||||||
|
工具:[golang-migrate](https://github.com/golang-migrate/migrate)
|
||||||
|
Mongo driver 讀的是 **JSON**(`db.runCommand` 陣列),不是 shell `.js`。
|
||||||
|
|
||||||
|
## 目錄
|
||||||
|
|
||||||
|
```text
|
||||||
|
generate/database/mongo/
|
||||||
|
000001_members_indexes.up.json
|
||||||
|
000001_members_indexes.down.json
|
||||||
|
000002_jobs_indexes.up.json
|
||||||
|
...
|
||||||
|
000003_seed_admin.up.json # 可選種子
|
||||||
|
```
|
||||||
|
|
||||||
|
命名:`{version}_{name}.up.json` / `.down.json`
|
||||||
|
|
||||||
|
## 指令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安裝 CLI(一次)
|
||||||
|
go install -tags 'mongodb' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
|
||||||
|
|
||||||
|
# 連線字串要含 database 名
|
||||||
|
export MONGO_URL='mongodb://127.0.0.1:27017/haixun'
|
||||||
|
|
||||||
|
# 套用全部
|
||||||
|
migrate -path generate/database/mongo -database "$MONGO_URL" up
|
||||||
|
|
||||||
|
# 回滾一步
|
||||||
|
migrate -path generate/database/mongo -database "$MONGO_URL" down 1
|
||||||
|
|
||||||
|
# 看版本
|
||||||
|
migrate -path generate/database/mongo -database "$MONGO_URL" version
|
||||||
|
```
|
||||||
|
|
||||||
|
Makefile:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make migrate-up
|
||||||
|
make migrate-down
|
||||||
|
```
|
||||||
|
|
||||||
|
## 何時寫什麼
|
||||||
|
|
||||||
|
| 類型 | 放哪 | 範例 |
|
||||||
|
|------|------|------|
|
||||||
|
| Index / collection 約束 | `*.up.json` createIndexes | members.uid unique |
|
||||||
|
| 種子資料 | 另開 version(如 000003_seed_*) | admin 帳 |
|
||||||
|
| 業務邏輯、密碼 runtime 產生 | **不要**寫進 migration | 用 API / 測試 helper |
|
||||||
|
|
||||||
|
種子 admin 預設:`admin@haixun.local` / `admin123`(hash 寫死在 up 檔;正式環境請改或不要跑 seed migration)。
|
||||||
|
|
||||||
|
## 與 gateway
|
||||||
|
|
||||||
|
先 `make migrate-up`,再 `go run . -f etc/gateway.yaml`(需 Mongo + Redis)。
|
||||||
|
## 不要
|
||||||
|
|
||||||
|
- 手寫 Go seeder/init
|
||||||
|
- 手寫 migration runner
|
||||||
|
- 把 stand-alone 電商 `.txt` 當 migrate 來源(格式不同;要用 JSON runCommand)
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"dropIndexes": "members",
|
||||||
|
"index": "uid_unique"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dropIndexes": "members",
|
||||||
|
"index": "email_unique"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dropIndexes": "members",
|
||||||
|
"index": "invite_code_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dropIndexes": "members",
|
||||||
|
"index": "status_roles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dropIndexes": "member_settings",
|
||||||
|
"index": "settings_uid_unique"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"createIndexes": "members",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": { "uid": 1 },
|
||||||
|
"name": "uid_unique",
|
||||||
|
"unique": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": { "email": 1 },
|
||||||
|
"name": "email_unique",
|
||||||
|
"unique": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": { "invite_code": 1 },
|
||||||
|
"name": "invite_code_1",
|
||||||
|
"sparse": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": { "status": 1, "roles": 1 },
|
||||||
|
"name": "status_roles"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"createIndexes": "member_settings",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": { "uid": 1 },
|
||||||
|
"name": "settings_uid_unique",
|
||||||
|
"unique": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"createIndexes": "counters",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": { "_id": 1 },
|
||||||
|
"name": "_id_"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"dropIndexes": "jobs",
|
||||||
|
"index": "owner_created"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dropIndexes": "jobs",
|
||||||
|
"index": "status_1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"createIndexes": "jobs",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": { "owner_uid": 1, "created_at": -1 },
|
||||||
|
"name": "owner_created"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": { "status": 1 },
|
||||||
|
"name": "status_1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"delete": "members",
|
||||||
|
"deletes": [
|
||||||
|
{
|
||||||
|
"q": { "uid": 1, "email": "admin@30cm.net" },
|
||||||
|
"limit": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"insert": "members",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"uid": 1000000,
|
||||||
|
"tenant_id": "default",
|
||||||
|
"email": "admin@30cm.net",
|
||||||
|
"display_name": "Harbor Admin",
|
||||||
|
"roles": ["admin", "member"],
|
||||||
|
"status": "active",
|
||||||
|
"email_verified": true,
|
||||||
|
"email_verified_at": 1700000000000000000,
|
||||||
|
"invite_code": "SEEDADMIN",
|
||||||
|
"password_hash": "$2a$10$iGGFNC5rM8MFhU8Wj6a7TOcQCsTeD03bkEODvQBh2ZxvCH.PcLl3O",
|
||||||
|
"timezone": "Asia/Taipei",
|
||||||
|
"notify_email": false,
|
||||||
|
"bio": "",
|
||||||
|
"created_at": 1700000000000000000,
|
||||||
|
"updated_at": 1700000000000000000,
|
||||||
|
"seed_note": "password is admin123 — change in production; uid=1000000 (8 digits from 1M)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ordered": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update": "counters",
|
||||||
|
"updates": [
|
||||||
|
{
|
||||||
|
"q": { "_id": "member_uid" },
|
||||||
|
"u": { "$setOnInsert": { "seq": 1000000 } },
|
||||||
|
"upsert": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"dropIndexes": "refresh_tokens",
|
||||||
|
"index": "refresh_uid_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dropIndexes": "refresh_tokens",
|
||||||
|
"index": "refresh_ttl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dropIndexes": "auth_codes",
|
||||||
|
"index": "auth_code_ttl"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"createIndexes": "refresh_tokens",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": { "uid": 1 },
|
||||||
|
"name": "refresh_uid_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": { "expires_at": 1 },
|
||||||
|
"name": "refresh_ttl",
|
||||||
|
"expireAfterSeconds": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"createIndexes": "auth_codes",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": { "expires_at": 1 },
|
||||||
|
"name": "auth_code_ttl",
|
||||||
|
"expireAfterSeconds": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"dropIndexes": "identities",
|
||||||
|
"index": "login_platform_unique"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dropIndexes": "identities",
|
||||||
|
"index": "identities_uid_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dropIndexes": "identities",
|
||||||
|
"index": "platform_account_type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dropIndexes": "members",
|
||||||
|
"index": "phone_1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"createIndexes": "identities",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": { "login_id": 1, "platform": 1 },
|
||||||
|
"name": "login_platform_unique",
|
||||||
|
"unique": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": { "uid": 1 },
|
||||||
|
"name": "identities_uid_1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": { "platform": 1, "account_type": 1 },
|
||||||
|
"name": "platform_account_type"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"createIndexes": "members",
|
||||||
|
"indexes": [
|
||||||
|
{
|
||||||
|
"key": { "phone": 1 },
|
||||||
|
"name": "phone_1",
|
||||||
|
"sparse": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"delete": "identities",
|
||||||
|
"deletes": [
|
||||||
|
{
|
||||||
|
"q": { "uid": 1, "login_id": "admin@haixun.local", "platform": "platform" },
|
||||||
|
"limit": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"insert": "identities",
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"uid": 1000000,
|
||||||
|
"login_id": "admin@haixun.local",
|
||||||
|
"platform": "platform",
|
||||||
|
"account_type": "email",
|
||||||
|
"password_hash": "$2a$10$iGGFNC5rM8MFhU8Wj6a7TOcQCsTeD03bkEODvQBh2ZxvCH.PcLl3O",
|
||||||
|
"created_at": 1700000000000000000,
|
||||||
|
"updated_at": 1700000000000000000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ordered": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"update": "members",
|
||||||
|
"updates": [
|
||||||
|
{
|
||||||
|
"q": { "uid": 1000000, "email": "admin@haixun.local" },
|
||||||
|
"u": { "$set": { "uid": 1 } },
|
||||||
|
"multi": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update": "identities",
|
||||||
|
"updates": [
|
||||||
|
{
|
||||||
|
"q": { "uid": 1000000, "login_id": "admin@haixun.local" },
|
||||||
|
"u": { "$set": { "uid": 1 } },
|
||||||
|
"multi": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"update": "counters",
|
||||||
|
"updates": [
|
||||||
|
{
|
||||||
|
"q": { "_id": "member_uid", "seq": { "$lt": 1000000 } },
|
||||||
|
"u": { "$set": { "seq": 1000000 } },
|
||||||
|
"upsert": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"q": { "_id": "member_uid" },
|
||||||
|
"u": { "$setOnInsert": { "seq": 1000000 } },
|
||||||
|
"upsert": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update": "members",
|
||||||
|
"updates": [
|
||||||
|
{
|
||||||
|
"q": { "uid": 1, "email": "admin@haixun.local" },
|
||||||
|
"u": { "$set": { "uid": 1000000 } },
|
||||||
|
"multi": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update": "identities",
|
||||||
|
"updates": [
|
||||||
|
{
|
||||||
|
"q": { "uid": 1, "login_id": "admin@haixun.local" },
|
||||||
|
"u": { "$set": { "uid": 1000000 } },
|
||||||
|
"multi": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update": "member_settings",
|
||||||
|
"updates": [
|
||||||
|
{
|
||||||
|
"q": { "uid": 1 },
|
||||||
|
"u": { "$set": { "uid": 1000000 } },
|
||||||
|
"multi": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"update": "refresh_tokens",
|
||||||
|
"updates": [
|
||||||
|
{
|
||||||
|
"q": { "uid": 1 },
|
||||||
|
"u": { "$set": { "uid": 1000000 } },
|
||||||
|
"multi": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl {{.version}}
|
||||||
|
|
||||||
|
package {{.PkgName}}
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
{{if .HasRequest}}"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
{{end}}{{.ImportPackages}}
|
||||||
|
)
|
||||||
|
|
||||||
|
{{if .HasDoc}}{{.Doc}}{{end}}
|
||||||
|
func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
{{if .HasRequest}}var req types.{{.RequestType}}
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
{{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx)
|
||||||
|
{{if .HasResp}}data, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
|
||||||
|
{{if .HasResp}}response.Write(r.Context(), w, data, err){{else}}response.Write(r.Context(), w, nil, err){{end}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
module apps/backend
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
|
github.com/stretchr/testify v1.10.0
|
||||||
|
github.com/zeromicro/go-zero v1.7.6
|
||||||
|
go.mongodb.org/mongo-driver v1.17.1
|
||||||
|
golang.org/x/crypto v0.54.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Masterminds/semver v1.4.2 // indirect
|
||||||
|
github.com/Masterminds/sprig v2.16.0+incompatible // indirect
|
||||||
|
github.com/PuerkitoBio/goquery v1.5.0 // indirect
|
||||||
|
github.com/andybalholm/cascadia v1.0.0 // indirect
|
||||||
|
github.com/aokoli/goutils v1.0.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.42.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.28 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.31 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.23 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.31 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.105.0 // indirect
|
||||||
|
github.com/aws/smithy-go v1.27.3 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gorilla/css v1.0.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||||
|
github.com/huandu/xstrings v1.2.0 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.6 // indirect
|
||||||
|
github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.9 // indirect
|
||||||
|
github.com/matcornic/hermes/v2 v2.1.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
|
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.20.5 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
|
github.com/prometheus/common v0.55.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
|
github.com/redis/go-redis/v9 v9.7.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||||
|
github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04 // indirect
|
||||||
|
github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe // indirect
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
|
golang.org/x/net v0.56.0 // indirect
|
||||||
|
golang.org/x/sync v0.22.0 // indirect
|
||||||
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
|
golang.org/x/text v0.40.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||||
|
google.golang.org/grpc v1.65.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.1 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,261 @@
|
||||||
|
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
|
||||||
|
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
|
github.com/Masterminds/sprig v2.16.0+incompatible h1:QZbMUPxRQ50EKAq3LFMnxddMu88/EUUG3qmxwtDmPsY=
|
||||||
|
github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||||
|
github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
|
||||||
|
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
||||||
|
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
|
||||||
|
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
|
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
|
||||||
|
github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqRWqM3nksiyXk5s8=
|
||||||
|
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
|
||||||
|
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
|
github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg=
|
||||||
|
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.42.1 h1:9eOTgu1z/dVtYpNZ3/8/XbbaX0x/BqE3HUzAzs6K0ek=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.42.1/go.mod h1:5pKeft2eJj+gElQ38Jqg4ibCqh+/AK33/0X3hip7IjM=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14 h1:3IZY0XAJquT3aHzbkHfPzy4ACPcEjVG0x87KOwtpqGY=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14/go.mod h1:zwM6veDkhGgQFqkBy+uT28AAYpLu+uFMlPl+rCg/73E=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.28 h1:zTXJSsNcoO91/mTXsZoYf0AK8dvNPiA58/VtyGXR+wM=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.28/go.mod h1:Kd9E0JzDBW/q1xbsHFrev/GnbAf5J0Ng8xoyc7HZ91Q=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 h1:xM/Is9cKMHa8Jj8zkvWhvrFkZsXJV9E+BB4g0HW0duQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30/go.mod h1:WueJeNDZvK1fMYEWJIkcivBfEzUkTpBhzlrUKKY8EuA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 h1:jn46zC9LdsVR/ZpMIJqMqb8hHv31BlLx3ulVqNspUOk=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30/go.mod h1:1hTMsAgbdS/AtUi4bw8+gUuh1pceo+eXRLfpSuSQj3M=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.31 h1:3GUprIsfmGcC5SACIyB0e7E0BM1O1b3Erl5CePYIAeQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.31/go.mod h1:7PuV1yl5e2xnUbm+RqvVg5i2iBM8EyijZNoI9wsOoOc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13 h1:mbRIur/BiHK6SKPjoBIXSE/hJ6g6JGRLuxQy1jGjlN4=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13/go.mod h1:ITg9em2KbJx1s0y4aqRX5OYWG6HBZ5TVR//OdpEZ2CQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.23 h1:9Fjh6fi/U5JEStVZijmaMpUwE/gvBJj7x2B/PjbO9To=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.23/go.mod h1:iMoT2f1tClxrWAAnKCXjZQ6LOmfLrMG14wmnWpM+F14=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30 h1:/Z5jmNrKsSD7EmDjzAPsm/3L9IuOkzaynklJZ1qX7S4=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30/go.mod h1:lEzEZnOosE7zi8Z6royW1cFJTD9fpab4Ul1SBrllewk=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.31 h1:uao4A3QZ5UmB326V6KF+qRpv9Tjz7IlnlnTbbANntlU=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.31/go.mod h1:I/1+z0VwL1GhQyLgkoHDlygpUZ+iTAwOQ/NsftiUL2I=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.105.0 h1:XptwLL+UHXgafYMIHTy59IRovLbhz3znkxY2uS/pbXU=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.105.0/go.mod h1:zdmCoFO/dSI7GlrwsPqFJI+WlFnSU4Tc8TJnlXrM1Do=
|
||||||
|
github.com/aws/smithy-go v1.27.3 h1:F3Zb497UhhskkfpJmfkXswyo+t0sh9OTBnIHjogWbVY=
|
||||||
|
github.com/aws/smithy-go v1.27.3/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
|
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
|
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||||
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||||
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||||
|
github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
|
||||||
|
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||||
|
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||||
|
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
|
github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0 h1:xqgexXAGQgY3HAjNPSaCqn5Aahbo5TKsmhp8VRfr1iQ=
|
||||||
|
github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||||
|
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/matcornic/hermes/v2 v2.1.0 h1:9TDYFBPFv6mcXanaDmRDEp/RTWj0dTTi+LpFnnnfNWc=
|
||||||
|
github.com/matcornic/hermes/v2 v2.1.0/go.mod h1:2+ziJeoyRfaLiATIL8VZ7f9hpzH4oDHqTmn0bhrsgVI=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||||
|
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
|
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
|
||||||
|
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
|
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||||
|
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||||
|
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||||
|
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||||
|
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||||
|
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||||
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
|
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||||
|
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||||
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04 h1:L0rPdfzq43+NV8rfIx2kA4iSSLRj2jN5ijYHoeXRwvQ=
|
||||||
|
github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04/go.mod h1:tcnB1voG49QhCrwq1W0w5hhGasvOg+VQp9i9H1rCM1w=
|
||||||
|
github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe h1:9YnI5plmy+ad6BM+JCLJb2ZV7/TNiE5l7SNKfumYKgc=
|
||||||
|
github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe/go.mod h1:JTFJA/t820uFDoyPpErFQ3rb3amdZoPtxcKervG0OE4=
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
|
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||||
|
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||||
|
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||||
|
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||||
|
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
|
github.com/zeromicro/go-zero v1.7.6 h1:SArK4xecdrpVY3ZFJcbc0IZCx+NuWyHNjCv9f1+Gwrc=
|
||||||
|
github.com/zeromicro/go-zero v1.7.6/go.mod h1:SmGykRm5e0Z4CGNj+GaSKDffaHzQV56fel0FkymTLlE=
|
||||||
|
go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
|
||||||
|
go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
|
||||||
|
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||||
|
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||||
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
||||||
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA=
|
||||||
|
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY=
|
||||||
|
go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM=
|
||||||
|
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||||
|
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
||||||
|
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||||
|
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||||
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
golang.org/x/crypto v0.0.0-20181029175232-7e6ffbd03851/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||||
|
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||||
|
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
|
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190225065934-cc5685c2db12/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
|
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||||
|
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||||
|
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||||
|
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||||
|
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||||
|
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
||||||
|
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
|
||||||
|
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
|
"github.com/zeromicro/go-zero/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config — go-zero 風格:RestConf + CacheRedis + Mongo URI.
|
||||||
|
type Config struct {
|
||||||
|
rest.RestConf
|
||||||
|
|
||||||
|
// CacheRedis is go-zero cache.CacheConf (feeds monc Redis 快取).
|
||||||
|
CacheRedis cache.CacheConf
|
||||||
|
|
||||||
|
Mongo struct {
|
||||||
|
URI string
|
||||||
|
Database string
|
||||||
|
}
|
||||||
|
|
||||||
|
Auth struct {
|
||||||
|
AccessSecret string
|
||||||
|
AccessExpire int64
|
||||||
|
RefreshSecret string
|
||||||
|
RefreshExpire int64
|
||||||
|
}
|
||||||
|
Platform struct {
|
||||||
|
AIKey string `json:",optional"`
|
||||||
|
ExaKey string `json:",optional"`
|
||||||
|
ThreadsAppId string `json:",optional"`
|
||||||
|
ThreadsAppSecret string `json:",optional"`
|
||||||
|
}
|
||||||
|
Worker struct {
|
||||||
|
ID string `json:",default=worker-1"`
|
||||||
|
PollIntervalMs int `json:",default=2000"`
|
||||||
|
}
|
||||||
|
// Bcrypt cost (stand-alone Bcrypt.Cost)
|
||||||
|
Bcrypt struct {
|
||||||
|
Cost int `json:",default=10"`
|
||||||
|
}
|
||||||
|
// OAuth third-party (optional; empty → mock path in callback)
|
||||||
|
OAuth struct {
|
||||||
|
GoogleClientID string `json:",optional"`
|
||||||
|
LineClientID string `json:",optional"`
|
||||||
|
LineClientSecret string `json:",optional"`
|
||||||
|
LineRedirectURI string `json:",optional"`
|
||||||
|
}
|
||||||
|
// Mail — 驗證碼/重設密碼寄信(SMTP;空 Host 則只打 log + 可回 mock_code)
|
||||||
|
Mail struct {
|
||||||
|
Sender string `json:",optional"` // e.g. "Harbor Desk <noreply@example.com>"
|
||||||
|
SMTP struct {
|
||||||
|
Host string `json:",optional"`
|
||||||
|
Port int `json:",default=587"`
|
||||||
|
User string `json:",optional"`
|
||||||
|
Password string `json:",optional"`
|
||||||
|
} `json:",optional"`
|
||||||
|
// DevExposeCode: API 回 mock_code(本機沒信箱時用)。正式請 false。
|
||||||
|
DevExposeCode bool `json:",default=true"`
|
||||||
|
} `json:",optional"`
|
||||||
|
// 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"`
|
||||||
|
// Brand — 郵件 chrome(hermes);LogoURL 空則用 PublicWebBase + /brand-mark.jpg
|
||||||
|
Brand struct {
|
||||||
|
Name string `json:",optional,default=Harbor Desk"`
|
||||||
|
LogoURL string `json:",optional"` // absolute URL
|
||||||
|
Copyright string `json:",optional,default=© Harbor Desk"`
|
||||||
|
} `json:",optional"`
|
||||||
|
// ObjectStorage — 頭像等二進位;**僅 MinIO / S3**(不落本機磁碟)
|
||||||
|
ObjectStorage struct {
|
||||||
|
Endpoint string `json:",optional"` // 必填,e.g. http://127.0.0.1:9000
|
||||||
|
Region string `json:",optional,default=us-east-1"`
|
||||||
|
Bucket string `json:",optional,default=haixun-assets"`
|
||||||
|
AccessKey string `json:",optional"`
|
||||||
|
SecretKey string `json:",optional"`
|
||||||
|
UsePathStyle bool `json:",optional,default=true"`
|
||||||
|
PublicBaseURL string `json:",optional"` // 公開讀取前綴,勿尾斜線
|
||||||
|
} `json:",optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyEnv overlays secrets from environment.
|
||||||
|
func (c *Config) ApplyEnv() {
|
||||||
|
if v := os.Getenv("MONGO_URI"); v != "" {
|
||||||
|
c.Mongo.URI = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("MONGO_DATABASE"); v != "" {
|
||||||
|
c.Mongo.Database = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("REDIS_HOST"); v != "" && len(c.CacheRedis) > 0 {
|
||||||
|
c.CacheRedis[0].RedisConf.Host = v
|
||||||
|
}
|
||||||
|
if len(c.CacheRedis) > 0 {
|
||||||
|
// monc 快取 Redis 帳密(本機 infra 常開 requirepass)
|
||||||
|
if v := os.Getenv("REDIS_PASSWORD"); v != "" {
|
||||||
|
c.CacheRedis[0].RedisConf.Pass = v
|
||||||
|
} else if v := os.Getenv("HAIXUN_REDIS_PASSWORD"); v != "" {
|
||||||
|
c.CacheRedis[0].RedisConf.Pass = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("HAIXUN_REDIS_ADDR"); v != "" {
|
||||||
|
c.CacheRedis[0].RedisConf.Host = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := os.Getenv("AUTH_ACCESS_SECRET"); v != "" {
|
||||||
|
c.Auth.AccessSecret = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("AUTH_REFRESH_SECRET"); v != "" {
|
||||||
|
c.Auth.RefreshSecret = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("PLATFORM_AI_KEY"); v != "" {
|
||||||
|
c.Platform.AIKey = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("PLATFORM_EXA_KEY"); v != "" {
|
||||||
|
c.Platform.ExaKey = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("WORKER_ID"); v != "" {
|
||||||
|
c.Worker.ID = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("PORT"); v != "" {
|
||||||
|
var p int
|
||||||
|
for _, ch := range v {
|
||||||
|
if ch < '0' || ch > '9' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p = p*10 + int(ch-'0')
|
||||||
|
}
|
||||||
|
if p > 0 {
|
||||||
|
c.Port = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Mail / SMTP(MAIL_* 優先;HAIXUN_SMTP_* 與 old/infra 相容)
|
||||||
|
if v := firstEnv("MAIL_SENDER", "HAIXUN_SMTP_FROM"); v != "" {
|
||||||
|
c.Mail.Sender = v
|
||||||
|
}
|
||||||
|
if v := firstEnv("MAIL_SMTP_HOST", "HAIXUN_SMTP_HOST"); v != "" {
|
||||||
|
c.Mail.SMTP.Host = v
|
||||||
|
}
|
||||||
|
if v := firstEnv("MAIL_SMTP_USER", "HAIXUN_SMTP_USERNAME"); v != "" {
|
||||||
|
c.Mail.SMTP.User = v
|
||||||
|
}
|
||||||
|
if v := firstEnv("MAIL_SMTP_PASSWORD", "HAIXUN_SMTP_PASSWORD"); v != "" {
|
||||||
|
c.Mail.SMTP.Password = v
|
||||||
|
}
|
||||||
|
if v := firstEnv("MAIL_SMTP_PORT", "HAIXUN_SMTP_PORT"); v != "" {
|
||||||
|
if p := parsePort(v); p > 0 {
|
||||||
|
c.Mail.SMTP.Port = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := os.Getenv("MAIL_DEV_EXPOSE_CODE"); v == "0" || v == "false" || v == "FALSE" {
|
||||||
|
c.Mail.DevExposeCode = false
|
||||||
|
} else if v == "1" || v == "true" || v == "TRUE" {
|
||||||
|
c.Mail.DevExposeCode = true
|
||||||
|
}
|
||||||
|
if v := firstEnv("PUBLIC_WEB_BASE", "HAIXUN_PUBLIC_WEB_BASE"); v != "" {
|
||||||
|
c.PublicWebBase = strings.TrimRight(v, "/")
|
||||||
|
}
|
||||||
|
if v := os.Getenv("MAIL_BRAND_NAME"); v != "" {
|
||||||
|
c.Brand.Name = v
|
||||||
|
}
|
||||||
|
if v := firstEnv("MAIL_LOGO_URL", "HAIXUN_MAIL_LOGO_URL"); v != "" {
|
||||||
|
c.Brand.LogoURL = v
|
||||||
|
}
|
||||||
|
if v := os.Getenv("MAIL_COPYRIGHT"); v != "" {
|
||||||
|
c.Brand.Copyright = v
|
||||||
|
}
|
||||||
|
// Object storage (HAIXUN_STORAGE_S3_* 與 old/infra 相容)
|
||||||
|
if v := firstEnv("OBJECT_STORAGE_ENDPOINT", "HAIXUN_STORAGE_S3_ENDPOINT"); v != "" {
|
||||||
|
c.ObjectStorage.Endpoint = v
|
||||||
|
}
|
||||||
|
if v := firstEnv("OBJECT_STORAGE_REGION", "HAIXUN_STORAGE_S3_REGION"); v != "" {
|
||||||
|
c.ObjectStorage.Region = v
|
||||||
|
}
|
||||||
|
if v := firstEnv("OBJECT_STORAGE_BUCKET", "HAIXUN_STORAGE_S3_BUCKET"); v != "" {
|
||||||
|
c.ObjectStorage.Bucket = v
|
||||||
|
}
|
||||||
|
if v := firstEnv("OBJECT_STORAGE_ACCESS_KEY", "HAIXUN_STORAGE_S3_ACCESS_KEY", "MINIO_ROOT_USER"); v != "" {
|
||||||
|
c.ObjectStorage.AccessKey = v
|
||||||
|
}
|
||||||
|
if v := firstEnv("OBJECT_STORAGE_SECRET_KEY", "HAIXUN_STORAGE_S3_SECRET_KEY", "MINIO_ROOT_PASSWORD"); v != "" {
|
||||||
|
c.ObjectStorage.SecretKey = v
|
||||||
|
}
|
||||||
|
if v := firstEnv("OBJECT_STORAGE_PUBLIC_BASE_URL", "HAIXUN_STORAGE_S3_PUBLIC_BASE_URL"); v != "" {
|
||||||
|
c.ObjectStorage.PublicBaseURL = strings.TrimRight(v, "/")
|
||||||
|
}
|
||||||
|
if v := os.Getenv("HAIXUN_STORAGE_S3_USE_PATH_STYLE"); v == "0" || v == "false" {
|
||||||
|
c.ObjectStorage.UsePathStyle = false
|
||||||
|
} else if v == "1" || v == "true" {
|
||||||
|
c.ObjectStorage.UsePathStyle = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstEnv(keys ...string) string {
|
||||||
|
for _, k := range keys {
|
||||||
|
if v := os.Getenv(k); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePort(v string) int {
|
||||||
|
var p int
|
||||||
|
for _, ch := range v {
|
||||||
|
if ch < '0' || ch > '9' {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
p = p*10 + int(ch-'0')
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
// SuccessCode is the project success envelope code (AGENTS.md / spec A-07).
|
||||||
|
// Migrated from stand-alone-service 10200 → unified 102000.
|
||||||
|
const SuccessCode int64 = 102000
|
||||||
|
|
||||||
|
const SuccessMessage = "success"
|
||||||
|
|
||||||
|
const TokenTypeBearer = "Bearer"
|
||||||
|
|
||||||
|
type ContextKey string
|
||||||
|
|
||||||
|
func (c ContextKey) String() string { return string(c) }
|
||||||
|
|
||||||
|
const (
|
||||||
|
UIDCode ContextKey = "uid"
|
||||||
|
RoleCode ContextKey = "role"
|
||||||
|
ScopeCode ContextKey = "scope"
|
||||||
|
DeviceIDCode ContextKey = "device_id"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestSuccessCode_Is102000(t *testing.T) {
|
||||||
|
if SuccessCode != 102000 {
|
||||||
|
t.Fatalf("SuccessCode=%d want 102000 (migrated from stand-alone 10200)", SuccessCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/admin"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminResetPasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminResetPasswordReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := admin.NewAdminResetPasswordLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.AdminResetPassword(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/admin"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateMemberHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminCreateMemberReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := admin.NewCreateMemberLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.CreateMember(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/admin"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetMemberHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminUidPath
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := admin.NewGetMemberLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.GetMember(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/admin"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListMemberIdentitiesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminUidPath
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := admin.NewListMemberIdentitiesLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.ListMemberIdentities(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/admin"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListMembersHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminListMembersReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := admin.NewListMembersLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.ListMembers(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/admin"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetEmailVerifiedHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminEmailVerifiedReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := admin.NewSetEmailVerifiedLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.SetEmailVerified(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/admin"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetRolesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminRolesReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := admin.NewSetRolesLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.SetRoles(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/admin"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetStatusHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminStatusReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := admin.NewSetStatusLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.SetStatus(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/admin"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetSuspendedHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AdminSuspendReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := admin.NewSetSuspendedLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.SetSuspended(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BindAccountHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AuthBindReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewBindAccountLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.BindAccount(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListIdentitiesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
l := auth.NewListIdentitiesLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.ListIdentities()
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AuthLoginReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewLoginLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.Login(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LogoutAllHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
l := auth.NewLogoutAllLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.LogoutAll()
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LogoutHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AuthLogoutReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewLogoutLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.Logout(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
l := auth.NewMeLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.Me()
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func OAuthCallbackHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AuthOAuthCallbackReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewOAuthCallbackLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.OAuthCallback(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func OAuthStartHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AuthOAuthProviderPath
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewOAuthStartLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.OAuthStart(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RefreshHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AuthRefreshReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewRefreshLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.Refresh(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AuthRegisterReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewRegisterLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.Register(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RequestPasswordResetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AuthRequestResetReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewRequestPasswordResetLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.RequestPasswordReset(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ResetPasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AuthResetPasswordReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewResetPasswordLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.ResetPassword(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SendEmailCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
l := auth.NewSendEmailCodeLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.SendEmailCode()
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UnbindAccountHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AuthUnbindReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewUnbindAccountLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.UnbindAccount(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UpdateProfileHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AuthProfilePatchReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewUpdateProfileLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.UpdateProfile(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UpdateProfilePutHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AuthProfilePatchReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewUpdateProfilePutLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.UpdateProfilePut(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/auth"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func VerifyEmailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AuthVerifyEmailReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewVerifyEmailLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.VerifyEmail(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 UploadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.MediaUploadReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := media.NewUploadLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.Upload(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/ping"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HealthHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
l := ping.NewHealthLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.Health()
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/ping"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PingHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
l := ping.NewPingLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.Ping()
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,237 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl 1.7.6
|
||||||
|
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
admin "apps/backend/internal/handler/admin"
|
||||||
|
auth "apps/backend/internal/handler/auth"
|
||||||
|
media "apps/backend/internal/handler/media"
|
||||||
|
ping "apps/backend/internal/handler/ping"
|
||||||
|
settings "apps/backend/internal/handler/settings"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||||
|
server.AddRoutes(
|
||||||
|
rest.WithMiddlewares(
|
||||||
|
[]rest.Middleware{serverCtx.AuthJWT, serverCtx.AdminAuth},
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/members",
|
||||||
|
Handler: admin.ListMembersHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/members",
|
||||||
|
Handler: admin.CreateMemberHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/members/:uid",
|
||||||
|
Handler: admin.GetMemberHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/members/:uid/email-verified",
|
||||||
|
Handler: admin.SetEmailVerifiedHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/members/:uid/identities",
|
||||||
|
Handler: admin.ListMemberIdentitiesHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/members/:uid/reset-password",
|
||||||
|
Handler: admin.AdminResetPasswordHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/members/:uid/roles",
|
||||||
|
Handler: admin.SetRolesHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/members/:uid/status",
|
||||||
|
Handler: admin.SetStatusHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/members/:uid/suspend",
|
||||||
|
Handler: admin.SetSuspendedHandler(serverCtx),
|
||||||
|
},
|
||||||
|
}...,
|
||||||
|
),
|
||||||
|
rest.WithPrefix("/api/v1/admin"),
|
||||||
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/login",
|
||||||
|
Handler: auth.LoginHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/logout",
|
||||||
|
Handler: auth.LogoutHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/oauth/:provider/callback",
|
||||||
|
Handler: auth.OAuthCallbackHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/oauth/:provider/start",
|
||||||
|
Handler: auth.OAuthStartHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/password/request-reset",
|
||||||
|
Handler: auth.RequestPasswordResetHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/password/reset",
|
||||||
|
Handler: auth.ResetPasswordHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/refresh",
|
||||||
|
Handler: auth.RefreshHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/register",
|
||||||
|
Handler: auth.RegisterHandler(serverCtx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rest.WithPrefix("/api/v1/auth"),
|
||||||
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
rest.WithMiddlewares(
|
||||||
|
[]rest.Middleware{serverCtx.AuthJWT},
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/bind",
|
||||||
|
Handler: auth.BindAccountHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/email/send-code",
|
||||||
|
Handler: auth.SendEmailCodeHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/email/verify",
|
||||||
|
Handler: auth.VerifyEmailHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/identities",
|
||||||
|
Handler: auth.ListIdentitiesHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/logout-all",
|
||||||
|
Handler: auth.LogoutAllHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/me",
|
||||||
|
Handler: auth.MeHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPatch,
|
||||||
|
Path: "/profile",
|
||||||
|
Handler: auth.UpdateProfileHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPut,
|
||||||
|
Path: "/profile",
|
||||||
|
Handler: auth.UpdateProfilePutHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/unbind",
|
||||||
|
Handler: auth.UnbindAccountHandler(serverCtx),
|
||||||
|
},
|
||||||
|
}...,
|
||||||
|
),
|
||||||
|
rest.WithPrefix("/api/v1/auth"),
|
||||||
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
rest.WithMiddlewares(
|
||||||
|
[]rest.Middleware{serverCtx.AuthJWT},
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/upload",
|
||||||
|
Handler: media.UploadHandler(serverCtx),
|
||||||
|
},
|
||||||
|
}...,
|
||||||
|
),
|
||||||
|
rest.WithPrefix("/api/v1/media"),
|
||||||
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/health",
|
||||||
|
Handler: ping.HealthHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/ping",
|
||||||
|
Handler: ping.PingHandler(serverCtx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rest.WithPrefix("/api/v1"),
|
||||||
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
rest.WithMiddlewares(
|
||||||
|
[]rest.Middleware{serverCtx.AuthJWT},
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/ai",
|
||||||
|
Handler: settings.GetAiHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPut,
|
||||||
|
Path: "/ai",
|
||||||
|
Handler: settings.SaveAiHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/models",
|
||||||
|
Handler: settings.ListModelsHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/placement",
|
||||||
|
Handler: settings.GetPlacementHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPut,
|
||||||
|
Path: "/placement",
|
||||||
|
Handler: settings.SavePlacementHandler(serverCtx),
|
||||||
|
},
|
||||||
|
}...,
|
||||||
|
),
|
||||||
|
rest.WithPrefix("/api/v1/settings"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/settings"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAiHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
l := settings.NewGetAiLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.GetAi()
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/settings"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetPlacementHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
l := settings.NewGetPlacementLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.GetPlacement()
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/settings"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListModelsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.ListModelsReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := settings.NewListModelsLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.ListModels(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/settings"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SaveAiHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.SaveAiReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := settings.NewSaveAiLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.SaveAi(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl <no value>
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"apps/backend/internal/logic/settings"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SavePlacementHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.SavePlacementReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
response.Write(r.Context(), w, nil, response.WrapRequestError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := settings.NewSavePlacementLogic(r.Context(), svcCtx)
|
||||||
|
data, err := l.SavePlacement(&req)
|
||||||
|
response.Write(r.Context(), w, data, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package mongo
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// MustMongoURI returns Mongo URI from arg or MONGO_URI env (for mon/monc).
|
||||||
|
func MustMongoURI(uri string) string {
|
||||||
|
if v := os.Getenv("MONGO_URI"); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if uri == "" {
|
||||||
|
panic("mongo: empty URI (set Mongo.URI or MONGO_URI)")
|
||||||
|
}
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Package securelog centralizes log hygiene for secrets.
|
||||||
|
//
|
||||||
|
// Policy:
|
||||||
|
// - Never log password / new_password / current_password / temporary_password
|
||||||
|
// - Never log password_hash, refresh_token, access_token, api keys
|
||||||
|
// - go-zero mon dumps full Mongo docs at info — call SilenceMongo() at process start
|
||||||
|
package securelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sensitiveJSONKeys matched case-insensitively as JSON object keys.
|
||||||
|
var sensitiveKeyPattern = regexp.MustCompile(
|
||||||
|
`(?i)("(?:password|new_password|current_password|temporary_password|password_hash|` +
|
||||||
|
`access_token|refresh_token|api_key|secret|authorization|cookie)"\s*:\s*)` +
|
||||||
|
`(?:"(?:\\.|[^"\\])*"|null|true|false|-?\d+(?:\.\d+)?)`,
|
||||||
|
)
|
||||||
|
|
||||||
|
// SilenceMongo disables go-zero mon command logging (including document dumps).
|
||||||
|
// monc uses mon under the hood; without this, ReplaceOne logs password_hash.
|
||||||
|
func SilenceMongo() {
|
||||||
|
mon.DisableLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedactJSON replaces known secret fields in a JSON blob with "[REDACTED]".
|
||||||
|
func RedactJSON(raw string) string {
|
||||||
|
if raw == "" {
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
return sensitiveKeyPattern.ReplaceAllString(raw, `${1}"[REDACTED]"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedactAny marshals v then redacts; for ad-hoc logx of structs.
|
||||||
|
func RedactAny(v any) string {
|
||||||
|
if v == nil {
|
||||||
|
return "null"
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return "[unserializable]"
|
||||||
|
}
|
||||||
|
return RedactJSON(string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaskPassword returns a fixed placeholder (never the input).
|
||||||
|
func MaskPassword(_ string) string { return "[REDACTED]" }
|
||||||
|
|
||||||
|
// IsSensitiveKey reports if a field name must never be logged.
|
||||||
|
func IsSensitiveKey(name string) bool {
|
||||||
|
n := strings.ToLower(strings.TrimSpace(name))
|
||||||
|
switch n {
|
||||||
|
case "password", "new_password", "current_password", "temporary_password",
|
||||||
|
"password_hash", "access_token", "refresh_token", "api_key", "secret",
|
||||||
|
"authorization", "cookie", "content": // media content is base64 blob
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package securelog
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestRedactJSON(t *testing.T) {
|
||||||
|
in := `{"email":"a@b.com","password":"Secret12!@#a","password_hash":"$2a$10$abc","new_password":"x","ok":true}`
|
||||||
|
out := RedactJSON(in)
|
||||||
|
if contains(out, "Secret12") || contains(out, "$2a$10$abc") || contains(out, `"x"`) {
|
||||||
|
t.Fatalf("still has secret: %s", out)
|
||||||
|
}
|
||||||
|
if !contains(out, "[REDACTED]") {
|
||||||
|
t.Fatalf("expected redacted: %s", out)
|
||||||
|
}
|
||||||
|
if !contains(out, "a@b.com") {
|
||||||
|
t.Fatalf("email should remain: %s", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(s, sub string) bool {
|
||||||
|
return len(s) >= len(sub) && (s == sub || len(sub) == 0 ||
|
||||||
|
(len(s) > 0 && (func() bool {
|
||||||
|
for i := 0; i+len(sub) <= len(s); i++ {
|
||||||
|
if s[i:i+len(sub)] == sub {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})()))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"apps/backend/internal/module/member/domain"
|
||||||
|
memberUC "apps/backend/internal/module/member/usecase"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AdminResetPasswordLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdminResetPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminResetPasswordLogic {
|
||||||
|
return &AdminResetPasswordLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AdminResetPasswordLogic) AdminResetPassword(req *types.AdminResetPasswordReq) (resp *types.AdminCreateMemberData, err error) {
|
||||||
|
uid, err := strconv.ParseInt(req.Uid, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tmp := req.NewPassword
|
||||||
|
if tmp == "" {
|
||||||
|
tmp = req.Password
|
||||||
|
}
|
||||||
|
if tmp == "" {
|
||||||
|
tmp = memberUC.GenerateTempPassword()
|
||||||
|
} else if err := memberUC.ValidatePasswordPolicy(tmp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m, err := l.svcCtx.Members.FindByUID(l.ctx, uid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
loginID := m.Email
|
||||||
|
if loginID == "" {
|
||||||
|
loginID = m.Phone
|
||||||
|
}
|
||||||
|
if loginID == "" {
|
||||||
|
return nil, domain.ErrNotFound
|
||||||
|
}
|
||||||
|
// prefer identity path; fall back to member hash + create identity
|
||||||
|
if err := l.svcCtx.Auth.UpdateUserToken(l.ctx, loginID, domain.PlatformPassword, tmp); err != nil {
|
||||||
|
hash, herr := memberUC.HashPassword(tmp, 10)
|
||||||
|
if herr != nil {
|
||||||
|
return nil, herr
|
||||||
|
}
|
||||||
|
m.PasswordHash = hash
|
||||||
|
m.UpdatedAt = domain.NowNano()
|
||||||
|
if err := l.svcCtx.Members.UpdateMember(l.ctx, m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_ = l.svcCtx.Members.CreateIdentity(l.ctx, &domain.Identity{
|
||||||
|
UID: uid, LoginID: loginID, Platform: domain.PlatformPassword,
|
||||||
|
AccountType: domain.AccountTypeEmail, PasswordHash: hash,
|
||||||
|
CreatedAt: domain.NowNano(), UpdatedAt: domain.NowNano(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
m, _ = l.svcCtx.Members.FindByUID(l.ctx, uid)
|
||||||
|
}
|
||||||
|
return &types.AdminCreateMemberData{User: *types.MemberFromModel(m), TemporaryPassword: tmp}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"apps/backend/internal/module/member/domain"
|
||||||
|
memberUC "apps/backend/internal/module/member/usecase"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateMemberLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCreateMemberLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateMemberLogic {
|
||||||
|
return &CreateMemberLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CreateMemberLogic) CreateMember(req *types.AdminCreateMemberReq) (resp *types.AdminCreateMemberData, err error) {
|
||||||
|
tmp := req.Password
|
||||||
|
if tmp == "" {
|
||||||
|
tmp = memberUC.GenerateTempPassword()
|
||||||
|
} else if err := memberUC.ValidatePasswordPolicy(tmp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
email := strings.ToLower(strings.TrimSpace(req.Email))
|
||||||
|
m, _, err := l.svcCtx.Auth.CreateUserAccount(l.ctx, domain.CreateLoginUserRequest{
|
||||||
|
LoginID: email, Platform: domain.PlatformPassword, AccountType: domain.AccountTypeEmail,
|
||||||
|
Password: tmp, DisplayName: req.DisplayName, Email: email, Phone: req.Phone,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// admin-created: apply roles / verified / extra profile
|
||||||
|
now := domain.NowNano()
|
||||||
|
if len(req.Roles) > 0 {
|
||||||
|
m.Roles = req.Roles
|
||||||
|
}
|
||||||
|
if req.EmailVerified {
|
||||||
|
m.EmailVerified = true
|
||||||
|
m.EmailVerifiedAt = &now
|
||||||
|
m.Status = domain.StatusActive
|
||||||
|
}
|
||||||
|
if req.Bio != "" {
|
||||||
|
m.Bio = req.Bio
|
||||||
|
}
|
||||||
|
if req.Nickname != "" {
|
||||||
|
m.Nickname = req.Nickname
|
||||||
|
}
|
||||||
|
if req.FullName != "" {
|
||||||
|
m.FullName = req.FullName
|
||||||
|
}
|
||||||
|
m.UpdatedAt = now
|
||||||
|
if err := l.svcCtx.Members.UpdateMember(l.ctx, m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ids, _ := l.svcCtx.Auth.ListIdentities(l.ctx, m.UID)
|
||||||
|
return &types.AdminCreateMemberData{
|
||||||
|
User: *types.MemberFromModelWithIdentities(m, ids),
|
||||||
|
TemporaryPassword: tmp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetMemberLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetMemberLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMemberLogic {
|
||||||
|
return &GetMemberLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GetMemberLogic) GetMember(req *types.AdminUidPath) (resp *types.MemberPublic, err error) {
|
||||||
|
uid, err := strconv.ParseInt(req.Uid, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m, err := l.svcCtx.Members.FindByUID(l.ctx, uid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return types.MemberFromModel(m), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListMemberIdentitiesLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListMemberIdentitiesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListMemberIdentitiesLogic {
|
||||||
|
return &ListMemberIdentitiesLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListMemberIdentitiesLogic) ListMemberIdentities(req *types.AdminUidPath) (resp *types.AdminMemberIdentitiesData, err error) {
|
||||||
|
uid, err := strconv.ParseInt(req.Uid, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ids, err := l.svcCtx.Auth.ListIdentities(l.ctx, uid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list := make([]types.IdentityPublic, 0, len(ids))
|
||||||
|
for _, id := range ids {
|
||||||
|
list = append(list, types.IdentityFromModel(id))
|
||||||
|
}
|
||||||
|
return &types.AdminMemberIdentitiesData{List: list}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListMembersLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListMembersLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListMembersLogic {
|
||||||
|
return &ListMembersLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListMembersLogic) ListMembers(req *types.AdminListMembersReq) (resp *types.AdminListData, err error) {
|
||||||
|
page, pageSize := req.Page, req.PageSize
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize < 1 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
list, total, err := l.svcCtx.Members.ListPage(l.ctx, page, pageSize, req.Query, req.Status)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items := make([]types.MemberPublic, 0, len(list))
|
||||||
|
for _, m := range list {
|
||||||
|
items = append(items, *types.MemberFromModel(m))
|
||||||
|
}
|
||||||
|
totalPages := int(total) / pageSize
|
||||||
|
if int(total)%pageSize != 0 {
|
||||||
|
totalPages++
|
||||||
|
}
|
||||||
|
if totalPages == 0 {
|
||||||
|
totalPages = 1
|
||||||
|
}
|
||||||
|
return &types.AdminListData{
|
||||||
|
List: items,
|
||||||
|
Pagination: types.Pagination{Page: page, PageSize: pageSize, Total: total, TotalPages: totalPages},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"apps/backend/internal/module/member/domain"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SetEmailVerifiedLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSetEmailVerifiedLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SetEmailVerifiedLogic {
|
||||||
|
return &SetEmailVerifiedLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SetEmailVerifiedLogic) SetEmailVerified(req *types.AdminEmailVerifiedReq) (resp *types.MemberPublic, err error) {
|
||||||
|
uid, err := strconv.ParseInt(req.Uid, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m, err := l.svcCtx.Members.FindByUID(l.ctx, uid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.EmailVerified = req.Verified
|
||||||
|
if req.Verified {
|
||||||
|
now := domain.NowNano()
|
||||||
|
m.EmailVerifiedAt = &now
|
||||||
|
if m.Status == domain.StatusUnverified {
|
||||||
|
m.Status = domain.StatusActive
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.EmailVerifiedAt = nil
|
||||||
|
}
|
||||||
|
m.UpdatedAt = domain.NowNano()
|
||||||
|
if err := l.svcCtx.Members.UpdateMember(l.ctx, m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return types.MemberFromModel(m), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"apps/backend/internal/module/member/domain"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SetRolesLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSetRolesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SetRolesLogic {
|
||||||
|
return &SetRolesLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SetRolesLogic) SetRoles(req *types.AdminRolesReq) (resp *types.MemberPublic, err error) {
|
||||||
|
uid, err := strconv.ParseInt(req.Uid, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m, err := l.svcCtx.Members.FindByUID(l.ctx, uid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
wasAdmin, willAdmin := m.IsAdmin(), false
|
||||||
|
for _, r := range req.Roles {
|
||||||
|
if r == domain.RoleAdmin {
|
||||||
|
willAdmin = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if wasAdmin && !willAdmin {
|
||||||
|
n, _ := l.svcCtx.Members.CountActiveAdmins(l.ctx)
|
||||||
|
if n <= 1 {
|
||||||
|
return nil, domain.ErrLastAdmin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.Roles = req.Roles
|
||||||
|
m.UpdatedAt = domain.NowNano()
|
||||||
|
if err := l.svcCtx.Members.UpdateMember(l.ctx, m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return types.MemberFromModel(m), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"apps/backend/internal/module/member/domain"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SetStatusLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSetStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SetStatusLogic {
|
||||||
|
return &SetStatusLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SetStatusLogic) SetStatus(req *types.AdminStatusReq) (resp *types.MemberPublic, err error) {
|
||||||
|
uid, err := strconv.ParseInt(req.Uid, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
status := req.Status
|
||||||
|
switch status {
|
||||||
|
case domain.StatusActive, domain.StatusSuspended, domain.StatusUnverified, domain.StatusUninitialized:
|
||||||
|
default:
|
||||||
|
return nil, domain.ErrNotFound // treat invalid as not found-ish; better weak password reuse? use ErrNotFound ok for now
|
||||||
|
}
|
||||||
|
m, err := l.svcCtx.Auth.UpdateStatus(l.ctx, uid, status)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return types.MemberFromModel(m), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"apps/backend/internal/middleware"
|
||||||
|
"apps/backend/internal/module/member/domain"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SetSuspendedLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSetSuspendedLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SetSuspendedLogic {
|
||||||
|
return &SetSuspendedLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SetSuspendedLogic) SetSuspended(req *types.AdminSuspendReq) (resp *types.MemberPublic, err error) {
|
||||||
|
actor, _ := middleware.UIDFrom(l.ctx)
|
||||||
|
uid, err := strconv.ParseInt(req.Uid, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if req.Suspended && uid == actor {
|
||||||
|
return nil, domain.ErrSelfSuspend
|
||||||
|
}
|
||||||
|
m, err := l.svcCtx.Members.FindByUID(l.ctx, uid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if req.Suspended && m.IsAdmin() {
|
||||||
|
n, _ := l.svcCtx.Members.CountActiveAdmins(l.ctx)
|
||||||
|
if n <= 1 {
|
||||||
|
return nil, domain.ErrLastAdmin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
status := domain.StatusActive
|
||||||
|
if req.Suspended {
|
||||||
|
status = domain.StatusSuspended
|
||||||
|
}
|
||||||
|
m, err = l.svcCtx.Auth.UpdateStatus(l.ctx, uid, status)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return types.MemberFromModel(m), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apps/backend/internal/middleware"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BindAccountLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBindAccountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindAccountLogic {
|
||||||
|
return &BindAccountLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *BindAccountLogic) BindAccount(req *types.AuthBindReq) (resp *types.IdentityPublic, err error) {
|
||||||
|
uid, _ := middleware.UIDFrom(l.ctx)
|
||||||
|
id, err := l.svcCtx.Auth.BindAccount(l.ctx, uid, req.LoginId, req.Platform, req.AccountType, req.Password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out := types.IdentityFromModel(id)
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apps/backend/internal/middleware"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListIdentitiesLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListIdentitiesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListIdentitiesLogic {
|
||||||
|
return &ListIdentitiesLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListIdentitiesLogic) ListIdentities() (resp *types.AuthIdentitiesData, err error) {
|
||||||
|
uid, _ := middleware.UIDFrom(l.ctx)
|
||||||
|
ids, err := l.svcCtx.Auth.ListIdentities(l.ctx, uid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list := make([]types.IdentityPublic, 0, len(ids))
|
||||||
|
for _, id := range ids {
|
||||||
|
list = append(list, types.IdentityFromModel(id))
|
||||||
|
}
|
||||||
|
return &types.AuthIdentitiesData{List: list}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"apps/backend/internal/module/member/domain"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic {
|
||||||
|
return &LoginLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LoginLogic) Login(req *types.AuthLoginReq) (resp *types.AuthSessionData, err error) {
|
||||||
|
loginID := strings.TrimSpace(req.LoginId)
|
||||||
|
if loginID == "" {
|
||||||
|
loginID = strings.TrimSpace(req.Email)
|
||||||
|
}
|
||||||
|
platform := req.Platform
|
||||||
|
if platform == "" {
|
||||||
|
platform = domain.PlatformPassword
|
||||||
|
}
|
||||||
|
m, pair, err := l.svcCtx.Auth.LoginPassword(l.ctx, loginID, req.Password, platform)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ids, _ := l.svcCtx.Auth.ListIdentities(l.ctx, m.UID)
|
||||||
|
return &types.AuthSessionData{
|
||||||
|
Tokens: types.TokenPairFromAuth(pair.AccessToken, pair.RefreshToken, pair.TokenType, pair.ExpiresIn),
|
||||||
|
Member: *types.MemberFromModelWithIdentities(m, ids),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apps/backend/internal/middleware"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogoutAllLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogoutAllLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogoutAllLogic {
|
||||||
|
return &LogoutAllLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LogoutAllLogic) LogoutAll() (resp *types.AuthLogoutAllData, err error) {
|
||||||
|
uid, _ := middleware.UIDFrom(l.ctx)
|
||||||
|
if err := l.svcCtx.Auth.LogoutAll(l.ctx, uid); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &types.AuthLogoutAllData{Ok: true}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogoutLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogoutLogic {
|
||||||
|
return &LogoutLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LogoutLogic) Logout(req *types.AuthLogoutReq) (resp *types.OkData, err error) {
|
||||||
|
l.svcCtx.Auth.Logout(req.RefreshToken)
|
||||||
|
return &types.OkData{Ok: true, Message: "logged out"}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apps/backend/internal/middleware"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MeLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MeLogic {
|
||||||
|
return &MeLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *MeLogic) Me() (resp *types.MemberPublic, err error) {
|
||||||
|
uid, _ := middleware.UIDFrom(l.ctx)
|
||||||
|
m, err := l.svcCtx.Auth.Me(l.ctx, uid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ids, _ := l.svcCtx.Auth.ListIdentities(l.ctx, m.UID)
|
||||||
|
return types.MemberFromModelWithIdentities(m, ids), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"apps/backend/internal/module/member/domain"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OAuthCallbackLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOAuthCallbackLogic(ctx context.Context, svcCtx *svc.ServiceContext) *OAuthCallbackLogic {
|
||||||
|
return &OAuthCallbackLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *OAuthCallbackLogic) OAuthCallback(req *types.AuthOAuthCallbackReq) (resp *types.AuthSessionData, err error) {
|
||||||
|
provider := strings.ToLower(strings.TrimSpace(req.Provider))
|
||||||
|
if provider == "" {
|
||||||
|
provider = domain.PlatformGoogle
|
||||||
|
}
|
||||||
|
|
||||||
|
var subject, email, name string
|
||||||
|
switch provider {
|
||||||
|
case domain.PlatformGoogle:
|
||||||
|
token := req.AccessToken
|
||||||
|
if token == "" {
|
||||||
|
token = req.IdToken
|
||||||
|
}
|
||||||
|
if token == "" {
|
||||||
|
token = req.Code
|
||||||
|
}
|
||||||
|
// allow mock: code or token "mock:sub:email"
|
||||||
|
if token == "" {
|
||||||
|
return nil, domain.ErrOAuthFailed
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(token, "mock:") && req.Code != "" && strings.HasPrefix(req.Code, "mock:") {
|
||||||
|
token = req.Code
|
||||||
|
}
|
||||||
|
subject, email, name, err = l.svcCtx.Auth.VerifyGoogleIDToken(l.ctx, token)
|
||||||
|
if err != nil {
|
||||||
|
// dev fallback when Google not configured: treat code as mock subject
|
||||||
|
if req.Code != "" {
|
||||||
|
subject = provider + "_" + req.Code
|
||||||
|
email = subject + "@oauth.local"
|
||||||
|
name = provider + " user"
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case domain.PlatformLine:
|
||||||
|
code := req.Code
|
||||||
|
if code == "" {
|
||||||
|
code = req.AccessToken
|
||||||
|
}
|
||||||
|
subject, name, email, err = l.svcCtx.Auth.LineCodeToProfile(l.ctx, code, req.RedirectUri)
|
||||||
|
if err != nil {
|
||||||
|
if code != "" {
|
||||||
|
subject = provider + "_" + code
|
||||||
|
name = provider + " user"
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// generic mock oauth
|
||||||
|
code := req.Code
|
||||||
|
if code == "" {
|
||||||
|
code = "anon"
|
||||||
|
}
|
||||||
|
subject = provider + "_" + code
|
||||||
|
email = subject + "@oauth.local"
|
||||||
|
name = provider + " user"
|
||||||
|
}
|
||||||
|
|
||||||
|
m, pair, err := l.svcCtx.Auth.LoginOAuth(l.ctx, provider, subject, email, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ids, _ := l.svcCtx.Auth.ListIdentities(l.ctx, m.UID)
|
||||||
|
return &types.AuthSessionData{
|
||||||
|
Tokens: types.TokenPairFromAuth(pair.AccessToken, pair.RefreshToken, pair.TokenType, pair.ExpiresIn),
|
||||||
|
Member: *types.MemberFromModelWithIdentities(m, ids),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OAuthStartLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOAuthStartLogic(ctx context.Context, svcCtx *svc.ServiceContext) *OAuthStartLogic {
|
||||||
|
return &OAuthStartLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *OAuthStartLogic) OAuthStart(req *types.AuthOAuthProviderPath) (resp *types.AuthOAuthStartData, err error) {
|
||||||
|
p := req.Provider
|
||||||
|
if p == "" {
|
||||||
|
p = "google"
|
||||||
|
}
|
||||||
|
return &types.AuthOAuthStartData{
|
||||||
|
AuthorizeUrl: "https://example.com/oauth/" + p + "/authorize?state=dev",
|
||||||
|
Provider: p,
|
||||||
|
State: "dev",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RefreshLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRefreshLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RefreshLogic {
|
||||||
|
return &RefreshLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *RefreshLogic) Refresh(req *types.AuthRefreshReq) (resp *types.AuthTokensOnly, err error) {
|
||||||
|
pair, err := l.svcCtx.Auth.Refresh(l.ctx, req.RefreshToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &types.AuthTokensOnly{
|
||||||
|
Tokens: types.TokenPairFromAuth(pair.AccessToken, pair.RefreshToken, pair.TokenType, pair.ExpiresIn),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apps/backend/internal/module/member/domain"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegisterLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic {
|
||||||
|
return &RegisterLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *RegisterLogic) Register(req *types.AuthRegisterReq) (resp *types.AuthSessionData, err error) {
|
||||||
|
m, pair, err := l.svcCtx.Auth.CreateUserAccount(l.ctx, domain.CreateLoginUserRequest{
|
||||||
|
LoginID: req.Email, Platform: domain.PlatformPassword, AccountType: domain.AccountTypeEmail,
|
||||||
|
Password: req.Password, DisplayName: req.DisplayName, Email: req.Email, Phone: req.Phone,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ids, _ := l.svcCtx.Auth.ListIdentities(l.ctx, m.UID)
|
||||||
|
return &types.AuthSessionData{
|
||||||
|
Tokens: types.TokenPairFromAuth(pair.AccessToken, pair.RefreshToken, pair.TokenType, pair.ExpiresIn),
|
||||||
|
Member: *types.MemberFromModelWithIdentities(m, ids),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
notifDomain "apps/backend/internal/module/notification/domain"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RequestPasswordResetLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRequestPasswordResetLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RequestPasswordResetLogic {
|
||||||
|
return &RequestPasswordResetLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestPasswordReset 產生重設碼 → 寄信。信箱不存在回錯誤、不寄。
|
||||||
|
func (l *RequestPasswordResetLogic) RequestPasswordReset(req *types.AuthRequestResetReq) (resp *types.AuthRequestResetData, err error) {
|
||||||
|
email := strings.ToLower(strings.TrimSpace(req.Email))
|
||||||
|
code, err := l.svcCtx.Auth.RequestPasswordReset(l.ctx, email)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
relPath := resetPath(email, code)
|
||||||
|
absURL := absoluteWebURL(l.svcCtx.Config.PublicWebBase, relPath)
|
||||||
|
|
||||||
|
name := strings.Split(email, "@")[0]
|
||||||
|
langRaw := req.Language
|
||||||
|
if m, findErr := l.svcCtx.Members.FindByEmail(l.ctx, email); findErr == nil && m != nil {
|
||||||
|
if m.DisplayName != "" {
|
||||||
|
name = m.DisplayName
|
||||||
|
}
|
||||||
|
if langRaw == "" && m.PreferredLanguage != "" {
|
||||||
|
langRaw = m.PreferredLanguage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lang := notifDomain.NormalizeLanguage(langRaw)
|
||||||
|
|
||||||
|
subject, html, err := l.svcCtx.Notification.RenderVerify(
|
||||||
|
notifDomain.TemplatePasswordReset,
|
||||||
|
lang,
|
||||||
|
notifDomain.VerifyTemplateVars{
|
||||||
|
Username: name,
|
||||||
|
VerifyCode: code,
|
||||||
|
ResetURL: absURL,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorf("render password reset template: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := l.svcCtx.Notification.SendEmail(l.ctx, notifDomain.EmailRequest{
|
||||||
|
RecipientEmail: email,
|
||||||
|
SenderEmail: l.svcCtx.Notification.SenderFrom(),
|
||||||
|
Subject: subject,
|
||||||
|
HTMLBody: html,
|
||||||
|
}); err != nil {
|
||||||
|
l.Errorf("send password reset to %s: %v", email, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.AuthRequestResetData{
|
||||||
|
Ok: true,
|
||||||
|
Message: "reset code sent",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetPath(email, code string) string {
|
||||||
|
q := url.Values{}
|
||||||
|
q.Set("email", email)
|
||||||
|
q.Set("token", code)
|
||||||
|
return "/reset-password?" + q.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
func absoluteWebURL(base, path string) string {
|
||||||
|
base = strings.TrimRight(strings.TrimSpace(base), "/")
|
||||||
|
if base == "" {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(path, "/") {
|
||||||
|
path = "/" + path
|
||||||
|
}
|
||||||
|
return base + path
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResetPasswordLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResetPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetPasswordLogic {
|
||||||
|
return &ResetPasswordLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ResetPasswordLogic) ResetPassword(req *types.AuthResetPasswordReq) (resp *types.OkData, err error) {
|
||||||
|
code := req.Code
|
||||||
|
if code == "" {
|
||||||
|
code = req.Token
|
||||||
|
}
|
||||||
|
if err := l.svcCtx.Auth.ResetPassword(l.ctx, req.Email, code, req.NewPassword); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &types.OkData{Ok: true, Message: "password updated"}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"apps/backend/internal/middleware"
|
||||||
|
notifDomain "apps/backend/internal/module/notification/domain"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SendEmailCodeLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSendEmailCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendEmailCodeLogic {
|
||||||
|
return &SendEmailCodeLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendEmailCode 產生驗證碼 → hermes 多語模板 → 寄信。不回傳驗證碼給前端。
|
||||||
|
func (l *SendEmailCodeLogic) SendEmailCode() (resp *types.AuthSendCodeData, err error) {
|
||||||
|
uid, _ := middleware.UIDFrom(l.ctx)
|
||||||
|
code, err := l.svcCtx.Auth.SendEmailCode(l.ctx, uid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := l.svcCtx.Members.FindByUID(l.ctx, uid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
to := strings.TrimSpace(m.Email)
|
||||||
|
if to == "" {
|
||||||
|
return &types.AuthSendCodeData{Ok: true, Message: "verification code issued"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name := m.DisplayName
|
||||||
|
if name == "" {
|
||||||
|
name = strings.Split(to, "@")[0]
|
||||||
|
}
|
||||||
|
lang := notifDomain.NormalizeLanguage(m.PreferredLanguage)
|
||||||
|
|
||||||
|
subject, html, err := l.svcCtx.Notification.RenderVerify(
|
||||||
|
notifDomain.TemplateEmailVerify,
|
||||||
|
lang,
|
||||||
|
notifDomain.VerifyTemplateVars{Username: name, VerifyCode: code},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorf("render email verify template: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := l.svcCtx.Notification.SendEmail(l.ctx, notifDomain.EmailRequest{
|
||||||
|
RecipientEmail: to,
|
||||||
|
SenderEmail: l.svcCtx.Notification.SenderFrom(),
|
||||||
|
Subject: subject,
|
||||||
|
HTMLBody: html,
|
||||||
|
}); err != nil {
|
||||||
|
l.Errorf("send email verify to %s: %v", to, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.AuthSendCodeData{
|
||||||
|
Ok: true,
|
||||||
|
Message: "verification code sent",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apps/backend/internal/middleware"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UnbindAccountLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUnbindAccountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UnbindAccountLogic {
|
||||||
|
return &UnbindAccountLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *UnbindAccountLogic) UnbindAccount(req *types.AuthUnbindReq) (resp *types.OkData, err error) {
|
||||||
|
uid, _ := middleware.UIDFrom(l.ctx)
|
||||||
|
if err := l.svcCtx.Auth.UnbindAccount(l.ctx, uid, req.LoginId, req.Platform); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &types.OkData{Ok: true, Message: "unbound"}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"apps/backend/internal/middleware"
|
||||||
|
"apps/backend/internal/response"
|
||||||
|
"apps/backend/internal/svc"
|
||||||
|
"apps/backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateProfileLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpdateProfileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateProfileLogic {
|
||||||
|
return &UpdateProfileLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *UpdateProfileLogic) UpdateProfile(req *types.AuthProfilePatchReq) (resp *types.MemberPublic, err error) {
|
||||||
|
// 頭像:nil=不改;""=清除;非空須為 media upload 回傳的 http(s) URL(禁止 data URL)
|
||||||
|
if req.AvatarUrl != nil {
|
||||||
|
u := strings.TrimSpace(*req.AvatarUrl)
|
||||||
|
if u != "" {
|
||||||
|
if strings.HasPrefix(u, "data:") || len(u) > 2048 {
|
||||||
|
return nil, response.Biz(400, 400001, "avatar_url must be a short http(s) URL from media upload, not data URL")
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(u, "http://") && !strings.HasPrefix(u, "https://") {
|
||||||
|
return nil, response.Biz(400, 400001, "avatar_url must be http(s)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 正規化空白
|
||||||
|
*req.AvatarUrl = u
|
||||||
|
}
|
||||||
|
uid, _ := middleware.UIDFrom(l.ctx)
|
||||||
|
m, err := l.svcCtx.Auth.UpdateUserInfo(l.ctx, uid, types.PatchFromAuthProfile(req))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ids, _ := l.svcCtx.Auth.ListIdentities(l.ctx, m.UID)
|
||||||
|
return types.MemberFromModelWithIdentities(m, ids), nil
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue