refactor: complete backend rewrite for multi-protocol proxy
Major rewrite of the Go backend to support:
- Three API format imports: openai, anthropic, compatible
- Three protocol conversions: Chat Completions, Responses, Messages
- Hub-and-spoke architecture with Chat as intermediate format
Deleted:
- opencat.go (old entry)
- store/, team/, pkg/team/, pkg/store/ (old data layer)
- internal/model/, internal/consts/ (old types)
- internal/service/team/, internal/controller/team/ (old handlers)
- llm/ (removed LLM client library, pure proxy mode)
- dist/, assets/ (old build artifacts)
Added:
- internal/store/ — 9 GORM models + multi-DB support
- internal/pkg/ — crypto (AES-GCM), apikey, jwt, ratelimit, resp, tokenizer
- internal/channel/ — channel selection, weighted LB, health checks
- internal/proxy/convert/ — 6 protocol conversion functions + SSE streaming
- internal/proxy/ — gateway with request dispatch and upstream selection
- internal/usage/ — async usage recorder with batch writes
- internal/api/ — management API (auth, users, keys, channels, models)
- Makefile for build/test/deploy
Fixed API to match frontend expectations:
- Login response wraps token in { data: { token } }
- GET /api/profile route added
- Profile response wraps user in { code, data }
- Role returned as number (10=admin, 1=user)
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
package error
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func ErrorData(message string) gin.H {
|
||||
return gin.H{
|
||||
"error": gin.H{
|
||||
"message": message,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
文档 https://www.microsoft.com/en-us/bing/apis/bing-web-search-api
|
||||
价格 https://www.microsoft.com/en-us/bing/apis/pricing
|
||||
|
||||
curl -H "Ocp-Apim-Subscription-Key: <yourkeygoeshere>" https://api.bing.microsoft.com/v7.0/search?q=今天上海天气怎么样
|
||||
curl -H "Ocp-Apim-Subscription-Key: 6fc7c97ebed54f75a5e383ee2272c917" https://api.bing.microsoft.com/v7.0/search?q=今天上海天气怎么样
|
||||
*/
|
||||
|
||||
package search
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
const (
|
||||
bingEndpoint = "https://api.bing.microsoft.com/v7.0/search"
|
||||
)
|
||||
|
||||
var subscriptionKey string
|
||||
|
||||
func init() {
|
||||
if os.Getenv("bing") != "" {
|
||||
subscriptionKey = os.Getenv("bing")
|
||||
} else {
|
||||
log.Println("bing key not found")
|
||||
}
|
||||
}
|
||||
|
||||
func BingSearch(searchParams SearchParams) (any, error) {
|
||||
params := url.Values{}
|
||||
params.Set("q", searchParams.Query)
|
||||
params.Set("count", "5")
|
||||
if searchParams.Num > 0 {
|
||||
params.Set("count", fmt.Sprintf("%d", searchParams.Num))
|
||||
}
|
||||
|
||||
reqURL, _ := url.Parse(bingEndpoint)
|
||||
reqURL.RawQuery = params.Encode()
|
||||
|
||||
req, _ := http.NewRequest("GET", reqURL.String(), nil)
|
||||
req.Header.Set("Ocp-Apim-Subscription-Key", subscriptionKey)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
fmt.Println("Error sending request:", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading response:", err)
|
||||
return nil, err
|
||||
}
|
||||
result := gjson.ParseBytes(body).Get("webPages.value")
|
||||
|
||||
return result.Raw, nil
|
||||
|
||||
}
|
||||
|
||||
type SearchParams struct {
|
||||
Query string `form:"q"`
|
||||
Num int `form:"num,default=5"`
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
文档 https://www.microsoft.com/en-us/bing/apis/bing-web-search-api
|
||||
价格 https://www.microsoft.com/en-us/bing/apis/pricing
|
||||
|
||||
curl -H "Ocp-Apim-Subscription-Key: <yourkeygoeshere>" https://api.bing.microsoft.com/v7.0/search?q=今天上海天气怎么样
|
||||
curl -H "Ocp-Apim-Subscription-Key: 6fc7c97ebed54f75a5e383ee2272c917" https://api.bing.microsoft.com/v7.0/search?q=今天上海天气怎么样
|
||||
*/
|
||||
|
||||
package search
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBingSearch(t *testing.T) {
|
||||
var searchParams = SearchParams{
|
||||
Query: "上海明天天气怎么样",
|
||||
Num: 3,
|
||||
}
|
||||
|
||||
t.Run("BingSearch", func(t *testing.T) {
|
||||
got, err := BingSearch(searchParams)
|
||||
if err != nil {
|
||||
t.Errorf("BingSearch() error = %v", err)
|
||||
return
|
||||
}
|
||||
t.Log(got)
|
||||
})
|
||||
|
||||
}
|
||||
-114
@@ -1,114 +0,0 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"opencatd-open/internal/model"
|
||||
"opencatd-open/pkg/config"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
// "gocloud.dev/mysql"
|
||||
// "gocloud.dev/postgres"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
|
||||
// "gorm.io/driver/sqlite"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var DB *gorm.DB
|
||||
|
||||
// var DBType consts.DBType
|
||||
var IsPostgres bool
|
||||
|
||||
func GetDB() *gorm.DB {
|
||||
return DB
|
||||
}
|
||||
|
||||
// InitDB 初始化数据库连接
|
||||
func InitDB(cfg *config.Config) (*gorm.DB, error) {
|
||||
var db *gorm.DB
|
||||
var err error
|
||||
// 从环境变量获取DSN
|
||||
dsn := cfg.DSN
|
||||
|
||||
if dsn == "" {
|
||||
log.Println("No DSN provided, using SQLite as default")
|
||||
db, err = initSQLite()
|
||||
}
|
||||
|
||||
// 解析DSN来确定数据库类型
|
||||
if strings.HasPrefix(dsn, "postgres://") {
|
||||
IsPostgres = true
|
||||
|
||||
cfg.DB_Type = "postgres"
|
||||
db, err = initPostgres(dsn)
|
||||
} else if strings.HasPrefix(dsn, "mysql://") {
|
||||
|
||||
cfg.DB_Type = "mysql"
|
||||
db, err = initMySQL(dsn)
|
||||
} else {
|
||||
if dsn != "" {
|
||||
return nil, fmt.Errorf("unsupported database type in DSN: %s", dsn)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
DB = db
|
||||
|
||||
if IsPostgres {
|
||||
err = db.AutoMigrate(&model.User{}, &model.Token{}, &model.ApiKey_PG{}, &model.Usage{}, &model.DailyUsage{}, &model.Passkey{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err = db.AutoMigrate(&model.User{}, &model.Token{}, &model.ApiKey{}, &model.Usage{}, &model.DailyUsage{}, &model.Passkey{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// initSQLite 初始化 SQLite 数据库
|
||||
func initSQLite() (*gorm.DB, error) {
|
||||
if _, err := os.Stat("db"); os.IsNotExist(err) {
|
||||
errDir := os.MkdirAll("db", 0755)
|
||||
if errDir != nil {
|
||||
log.Fatalln("Error creating directory:", err)
|
||||
}
|
||||
}
|
||||
db, err := gorm.Open(sqlite.Open("./db/openteam.db"), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to SQLite: %v", err)
|
||||
}
|
||||
// db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// initPostgres 初始化 PostgreSQL 数据库
|
||||
func initPostgres(dsn string) (*gorm.DB, error) {
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to PostgreSQL: %v", err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// initMySQL 初始化 MySQL 数据库
|
||||
func initMySQL(dsn string) (*gorm.DB, error) {
|
||||
// 移除 "mysql://" 前缀,因为 MySQL 驱动不需要这个前缀
|
||||
dsn = strings.TrimPrefix(dsn, "mysql://")
|
||||
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to MySQL: %v", err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/bluele/gcache"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// WebAuthnSessionStore 使用 gcache 存储 WebAuthn 会话数据
|
||||
type WebAuthnSessionStore struct {
|
||||
cache gcache.Cache
|
||||
}
|
||||
|
||||
// NewWebAuthnSessionStore 创建一个新的会话存储实例
|
||||
func NewWebAuthnSessionStore() *WebAuthnSessionStore {
|
||||
// 创建一个 LRU 缓存,最多存储 10000 个会话,每个会话有效期 5 分钟
|
||||
gc := gcache.New(10000).
|
||||
LRU().
|
||||
Expiration(5 * time.Minute).
|
||||
Build()
|
||||
return &WebAuthnSessionStore{cache: gc}
|
||||
}
|
||||
|
||||
// GenerateSessionID 生成唯一的会话ID
|
||||
func GenerateSessionID() string {
|
||||
return uuid.NewString()
|
||||
}
|
||||
|
||||
// SaveWebauthnSession 保存 WebAuthn 会话数据
|
||||
func (s *WebAuthnSessionStore) SaveWebauthnSession(sessionID string, data *webauthn.SessionData) error {
|
||||
return s.cache.Set(sessionID, data)
|
||||
}
|
||||
|
||||
// GetWebauthnSession 获取 WebAuthn 会话数据
|
||||
func (s *WebAuthnSessionStore) GetWebauthnSession(sessionID string) (*webauthn.SessionData, error) {
|
||||
val, err := s.cache.Get(sessionID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gcache.KeyNotFoundError) {
|
||||
return nil, errors.New("会话未找到或已过期")
|
||||
}
|
||||
return nil, err // 其他 gcache 错误
|
||||
}
|
||||
|
||||
sessionData, ok := val.(*webauthn.SessionData)
|
||||
if !ok {
|
||||
// 如果类型断言失败,说明缓存中存储了错误类型的数据
|
||||
log.Printf("警告:会话存储中发现非预期的类型,Key: %s", sessionID)
|
||||
// 尝试删除无效数据
|
||||
_ = s.cache.Remove(sessionID)
|
||||
return nil, errors.New("无效的会话数据类型")
|
||||
}
|
||||
return sessionData, nil
|
||||
}
|
||||
|
||||
// DeleteWebauthnSession 删除 WebAuthn 会话数据
|
||||
func (s *WebAuthnSessionStore) DeleteWebauthnSession(sessionID string) {
|
||||
s.cache.Remove(sessionID)
|
||||
}
|
||||
|
||||
func (s *WebAuthnSessionStore) GetALL() map[any]any {
|
||||
|
||||
return s.cache.GetALL(false)
|
||||
}
|
||||
-182
@@ -1,182 +0,0 @@
|
||||
package team
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"opencatd-open/llm/azureopenai"
|
||||
"opencatd-open/store"
|
||||
"strings"
|
||||
|
||||
"github.com/Sakurasan/to"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Key struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ApiType string `json:"api_type,omitempty"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||
CreatedAt string `json:"createdAt,omitempty"`
|
||||
}
|
||||
|
||||
func HandleKeys(c *gin.Context) {
|
||||
keys, err := store.GetAllKeys()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, keys)
|
||||
}
|
||||
|
||||
func HandleAddKey(c *gin.Context) {
|
||||
var body Key
|
||||
if err := c.BindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
body.Name = strings.ToLower(strings.TrimSpace(body.Name))
|
||||
body.Key = strings.TrimSpace(body.Key)
|
||||
if strings.HasPrefix(body.Name, "azure.") {
|
||||
keynames := strings.Split(body.Name, ".")
|
||||
if len(keynames) < 2 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
|
||||
"message": "Invalid Key Name",
|
||||
}})
|
||||
return
|
||||
}
|
||||
k := &store.Key{
|
||||
ApiType: "azure",
|
||||
Name: body.Name,
|
||||
Key: body.Key,
|
||||
ResourceNmae: keynames[1],
|
||||
EndPoint: body.Endpoint,
|
||||
}
|
||||
if err := store.CreateKey(k); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
} else if strings.HasPrefix(body.Name, "claude.") {
|
||||
keynames := strings.Split(body.Name, ".")
|
||||
if len(keynames) < 2 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
|
||||
"message": "Invalid Key Name",
|
||||
}})
|
||||
return
|
||||
}
|
||||
if body.Endpoint == "" {
|
||||
body.Endpoint = "https://api.anthropic.com"
|
||||
}
|
||||
k := &store.Key{
|
||||
// ApiType: "anthropic",
|
||||
ApiType: "claude",
|
||||
Name: body.Name,
|
||||
Key: body.Key,
|
||||
ResourceNmae: keynames[1],
|
||||
EndPoint: body.Endpoint,
|
||||
}
|
||||
if err := store.CreateKey(k); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
} else if strings.HasPrefix(body.Name, "google.") {
|
||||
keynames := strings.Split(body.Name, ".")
|
||||
if len(keynames) < 2 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
|
||||
"message": "Invalid Key Name",
|
||||
}})
|
||||
return
|
||||
}
|
||||
|
||||
k := &store.Key{
|
||||
// ApiType: "anthropic",
|
||||
ApiType: "google",
|
||||
Name: body.Name,
|
||||
Key: body.Key,
|
||||
ResourceNmae: keynames[1],
|
||||
EndPoint: body.Endpoint,
|
||||
}
|
||||
if err := store.CreateKey(k); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
} else if strings.HasPrefix(body.Name, "github.") {
|
||||
keynames := strings.Split(body.Name, ".")
|
||||
if len(keynames) < 2 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{
|
||||
"message": "Invalid Key Name",
|
||||
}})
|
||||
return
|
||||
}
|
||||
|
||||
k := &store.Key{
|
||||
ApiType: "github",
|
||||
Name: body.Name,
|
||||
Key: body.Key,
|
||||
ResourceNmae: keynames[1],
|
||||
EndPoint: body.Endpoint,
|
||||
}
|
||||
if err := store.CreateKey(k); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if body.ApiType == "" {
|
||||
if err := store.AddKey("openai", body.Key, body.Name); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
k := &store.Key{
|
||||
ApiType: body.ApiType,
|
||||
Name: body.Name,
|
||||
Key: body.Key,
|
||||
ResourceNmae: azureopenai.GetResourceName(body.Endpoint),
|
||||
EndPoint: body.Endpoint,
|
||||
}
|
||||
if err := store.CreateKey(k); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
k, err := store.GetKeyrByName(body.Name)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{
|
||||
"message": err.Error(),
|
||||
}})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, k)
|
||||
}
|
||||
|
||||
func HandleDelKey(c *gin.Context) {
|
||||
id := to.Int(c.Param("id"))
|
||||
if id < 1 {
|
||||
c.JSON(http.StatusOK, gin.H{"error": "invalid key id"})
|
||||
return
|
||||
}
|
||||
if err := store.DeleteKey(uint(id)); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"error": "invalid key id"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "ok"})
|
||||
}
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
package team
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"opencatd-open/store"
|
||||
"time"
|
||||
|
||||
"github.com/Sakurasan/to"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Handleinit(c *gin.Context) {
|
||||
user, err := store.GetUserByID(1)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
u := store.User{Name: "root", Token: uuid.NewString()}
|
||||
u.ID = 1
|
||||
if err := store.CreateUser(&u); err != nil {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
rootToken = u.Token
|
||||
resJSON := User{
|
||||
false,
|
||||
int(u.ID),
|
||||
u.UpdatedAt.Format(time.RFC3339),
|
||||
u.Name,
|
||||
u.Token,
|
||||
u.CreatedAt.Format(time.RFC3339),
|
||||
}
|
||||
c.JSON(http.StatusOK, resJSON)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if user.ID == 1 {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "super user already exists, use cli to reset password",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func HandleMe(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
u, err := store.GetUserByToken(token[7:])
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
resJSON := User{
|
||||
false,
|
||||
int(u.ID),
|
||||
u.UpdatedAt.Format(time.RFC3339),
|
||||
u.Name,
|
||||
u.Token,
|
||||
u.CreatedAt.Format(time.RFC3339),
|
||||
}
|
||||
c.JSON(http.StatusOK, resJSON)
|
||||
}
|
||||
|
||||
func HandleMeUsage(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
fromStr := c.Query("from")
|
||||
toStr := c.Query("to")
|
||||
getMonthStartAndEnd := func() (start, end string) {
|
||||
loc, _ := time.LoadLocation("Local")
|
||||
now := time.Now().In(loc)
|
||||
|
||||
year, month, _ := now.Date()
|
||||
|
||||
startOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, loc)
|
||||
endOfMonth := startOfMonth.AddDate(0, 1, 0)
|
||||
|
||||
start = startOfMonth.Format("2006-01-02")
|
||||
end = endOfMonth.Format("2006-01-02")
|
||||
return
|
||||
}
|
||||
if fromStr == "" || toStr == "" {
|
||||
fromStr, toStr = getMonthStartAndEnd()
|
||||
}
|
||||
user, err := store.GetUserByToken(token)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusForbidden, err)
|
||||
return
|
||||
}
|
||||
usage, err := store.QueryUserUsage(to.String(user.ID), fromStr, toStr)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusForbidden, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, usage)
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package team
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"opencatd-open/store"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
rootToken string
|
||||
)
|
||||
|
||||
func AuthMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if rootToken == "" {
|
||||
u, err := store.GetUserByID(uint(1))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
rootToken = u.Token
|
||||
}
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" || token[:7] != "Bearer " {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if store.IsExistAuthCache(token[7:]) {
|
||||
if strings.HasPrefix(c.Request.URL.Path, "/1/me") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
if token[7:] != rootToken {
|
||||
u, err := store.GetUserByID(uint(1))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if token[:7] != u.Token {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
rootToken = u.Token
|
||||
store.LoadAuthCache()
|
||||
}
|
||||
// 可以在这里对 token 进行验证并检查权限
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func CORS() gin.HandlerFunc {
|
||||
config := cors.DefaultConfig()
|
||||
config.AllowAllOrigins = true
|
||||
config.AllowCredentials = true
|
||||
config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}
|
||||
config.AllowHeaders = []string{"*"}
|
||||
return cors.New(config)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package team
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"opencatd-open/store"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func HandleUsage(c *gin.Context) {
|
||||
fromStr := c.Query("from")
|
||||
toStr := c.Query("to")
|
||||
getMonthStartAndEnd := func() (start, end string) {
|
||||
loc, _ := time.LoadLocation("Local")
|
||||
now := time.Now().In(loc)
|
||||
|
||||
year, month, _ := now.Date()
|
||||
|
||||
startOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, loc)
|
||||
endOfMonth := startOfMonth.AddDate(0, 1, 0)
|
||||
|
||||
start = startOfMonth.Format("2006-01-02")
|
||||
end = endOfMonth.Format("2006-01-02")
|
||||
return
|
||||
}
|
||||
if fromStr == "" || toStr == "" {
|
||||
fromStr, toStr = getMonthStartAndEnd()
|
||||
}
|
||||
|
||||
usage, err := store.QueryUsage(fromStr, toStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, usage)
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package team
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"opencatd-open/store"
|
||||
|
||||
"github.com/Sakurasan/to"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
IsDelete bool `json:"IsDelete,omitempty"`
|
||||
ID int `json:"id,omitempty"`
|
||||
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
CreatedAt string `json:"createdAt,omitempty"`
|
||||
}
|
||||
|
||||
func HandleUsers(c *gin.Context) {
|
||||
users, err := store.GetAllUsers()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, users)
|
||||
}
|
||||
|
||||
func HandleAddUser(c *gin.Context) {
|
||||
var body User
|
||||
if err := c.BindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if len(body.Name) == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"error": "invalid user name"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := store.AddUser(body.Name, uuid.NewString()); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
u, err := store.GetUserByName(body.Name)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, u)
|
||||
}
|
||||
|
||||
func HandleDelUser(c *gin.Context) {
|
||||
id := to.Int(c.Param("id"))
|
||||
if id <= 1 {
|
||||
c.JSON(http.StatusOK, gin.H{"error": "invalid user id"})
|
||||
return
|
||||
}
|
||||
if err := store.DeleteUser(uint(id)); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "ok"})
|
||||
}
|
||||
|
||||
func HandleResetUserToken(c *gin.Context) {
|
||||
id := to.Int(c.Param("id"))
|
||||
newtoken := c.Query("token")
|
||||
if newtoken == "" {
|
||||
newtoken = uuid.NewString()
|
||||
}
|
||||
|
||||
if err := store.UpdateUser(uint(id), newtoken); err != nil {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
u, err := store.GetUserByID(uint(id))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if u.ID == 1 {
|
||||
rootToken = u.Token
|
||||
}
|
||||
c.JSON(http.StatusOK, u)
|
||||
}
|
||||
Reference in New Issue
Block a user