refactor: move backend files to backend/ directory

Reorganize project structure:
- backend/cmd/openteam/ — entry point
- backend/internal/ — core packages
- backend/middleware/ — HTTP middleware
- backend/router/ — route setup
- backend/wire/ — dependency injection
- backend/pkg/ — shared utilities
- backend/go.mod, go.sum — Go module files

Updated Makefile to work from backend/ directory.
Removed old lowercase makefile.
This commit is contained in:
Sakurasan
2026-08-30 12:02:52 +08:00
parent ef3025dd80
commit 902ecaeacc
64 changed files with 12 additions and 107 deletions
+6
View File
@@ -0,0 +1,6 @@
package dto
type BatchIDRequest struct {
UserID *int64 `json:"user_id"`
IDs []int64 `json:"ids" binding:"required"`
}
+20
View File
@@ -0,0 +1,20 @@
package dto
import (
"github.com/gin-gonic/gin"
)
type Error struct {
Code int `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
func WrapErrorAsOpenAI(c *gin.Context, code int, msg string) {
c.JSON(code, gin.H{
"error": Error{
Code: code,
Message: msg,
},
})
c.Abort()
}
+107
View File
@@ -0,0 +1,107 @@
package dto
import (
"errors"
"regexp"
"time"
validation "github.com/go-ozzo/ozzo-validation/v4"
)
// TeamKey 结构体定义
type TeamKey struct {
ID *int64 `json:"id,omitempty"`
UserID *int64 `json:"userID,omitempty"`
Name *string `json:"name,omitempty"` // 必须
Key *string `json:"key,omitempty"`
Status *int64 `json:"status,omitempty"` // 默认1 允许,0禁止
Quota *int64 `json:"quota,omitempty"` // UnlimitedQuota不为1 的时候必须
UnlimitedQuota *bool `json:"unlimitedQuota,omitempty"` // 默认1 不限制,0限制
UsedQuota *int64 `json:"usedQuota,omitempty"`
CreatedAt *int64 `json:"createdAt,omitempty"`
ExpiredAt *int64 `json:"expiredAt,omitempty"` // 可选
}
// DefaultTeamKey 创建一个具有默认值的 TeamKey
func DefaultTeamKey() TeamKey {
status := int64(1) // 默认允许
unlimitedQuota := true // 默认不限制
createdAt := time.Now().Unix()
return TeamKey{
Status: &status,
UnlimitedQuota: &unlimitedQuota,
CreatedAt: &createdAt,
}
}
// Validate 验证 TeamKey 结构体
func (t TeamKey) Validate() error {
// 自定义验证规则
var quotaRule validation.Rule = validation.Skip
if t.UnlimitedQuota != nil && !*t.UnlimitedQuota {
quotaRule = validation.Required.Error("当 UnlimitedQuota 为 false 时,Quota 是必填项")
}
// 过期时间校验
var expiredAtRule validation.Rule = validation.Skip
if t.ExpiredAt != nil {
expiredAtRule = validation.Min(time.Now().Unix()).Error("过期时间不能早于当前时间")
}
return validation.ValidateStruct(&t,
// ID 通常由系统生成,不需要验证
// UserID 可选,但如果提供必须大于 0
validation.Field(&t.UserID,
validation.When(t.UserID != nil, validation.Min(int64(1)).Error("用户 ID 必须大于 0"))),
// Name 是必填字段
validation.Field(&t.Name,
validation.Required.Error("名称不能为空"),
validation.When(t.Name != nil, validation.Length(1, 100).Error("名称长度应在 1-100 之间"))),
// Key 可选,但如果提供需要符合特定格式
validation.Field(&t.Key,
validation.When(t.Key != nil,
validation.Length(1, 255).Error("Key 长度应在 1-255 之间")),
validation.Match(regexp.MustCompile(`^[^\s]+$`)).Error("Key 不能包含空格"),
),
// Status 只能是 0 或 1
validation.Field(&t.Status,
validation.When(t.Status != nil, validation.In(int64(0), int64(1)).Error("状态只能是 0(禁止) 或 1(允许)"))),
// Quota 要求依赖于 UnlimitedQuota
validation.Field(&t.Quota, quotaRule,
validation.When(t.Quota != nil, validation.Min(int64(1)).Error("配额必须大于 0"))),
// UnlimitedQuota 是否限制配额
validation.Field(&t.UnlimitedQuota),
// UsedQuota 系统维护,不需要验证
validation.Field(&t.UsedQuota,
validation.When(t.UsedQuota != nil, validation.Min(int64(0)).Error("已使用配额不能为负数"))),
// CreatedAt 系统维护,不需要验证
validation.Field(&t.CreatedAt),
// ExpiredAt 可选,但如果提供必须大于当前时间
validation.Field(&t.ExpiredAt, expiredAtRule),
)
}
// ValidateCreate 创建时的特殊验证
func (t TeamKey) ValidateCreate() error {
// 首先进行基本验证
if err := t.Validate(); err != nil {
return err
}
// 创建时的额外验证
if t.Name == nil {
return errors.New("创建时必须提供名称")
}
return nil
}
+11
View File
@@ -0,0 +1,11 @@
package dto
type Passkey struct {
ID int64 `json:"id" gorm:"column:id;primaryKey;autoIncrement"`
Name string `json:"name" gorm:"column:name"` // 凭证名称,用于用户识别不同的设备
SignCount uint32 `json:"sign_count" gorm:"column:sign_count"` // 签名计数器,用于防止重放攻击
DeviceType string `json:"device_type" gorm:"column:device_type"` // 设备类型,如"platform"或"cross-platform"
LastUsedAt int64 `json:"last_used_at" gorm:"column:last_used_at"` // 最后使用时间
CreatedAt int64 `json:"created_at,omitempty" gorm:"autoCreateTime"`
UpdatedAt int64 `json:"updated_at,omitempty" gorm:"autoUpdateTime"`
}
+28
View File
@@ -0,0 +1,28 @@
package dto
import (
"net/http"
"github.com/gin-gonic/gin"
)
type Result struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data any `json:"data,omitempty"`
}
func Success(ctx *gin.Context, data any) {
ctx.JSON(http.StatusOK, Result{
Code: 200,
Data: data,
Msg: "success",
})
}
func Fail(c *gin.Context, code int, err string) {
c.AbortWithStatusJSON(code, gin.H{
"code": code,
"error": err,
})
}
+16
View File
@@ -0,0 +1,16 @@
package dto
type User struct {
Username string `json:"username" binding:"required,min=3,max=32"`
Password string `json:"password" binding:"required,min=4"`
}
type Auth struct {
Token string `json:"token"`
ExpiresIn int64 `json:"expires_in"`
}
type ChangePassword struct {
Password string `json:"password" binding:"required,min=4"`
NewPassword string `json:"newpassword" binding:"required,min=4"`
}