From 45708ca4f0929c6b47905932d6b8f09a2bb100b6 Mon Sep 17 00:00:00 2001 From: veypi Date: Sat, 14 Feb 2026 16:32:39 +0800 Subject: [PATCH] remove old --- api/app/access/create.go | 45 ---------- api/app/access/del.go | 38 --------- api/app/access/get.go | 30 ------- api/app/access/init.go | 14 ---- api/app/access/list.go | 64 -------------- api/app/access/patch.go | 56 ------------- api/app/app.go | 167 ------------------------------------- api/app/app_user/create.go | 29 ------- api/app/app_user/del.go | 37 -------- api/app/app_user/get.go | 24 ------ api/app/app_user/init.go | 14 ---- api/app/app_user/list.go | 47 ----------- api/app/app_user/patch.go | 35 -------- api/app/init.go | 27 ------ api/app/resource/create.go | 31 ------- api/app/resource/del.go | 36 -------- api/app/resource/get.go | 24 ------ api/app/resource/init.go | 14 ---- api/app/resource/list.go | 50 ----------- api/app/resource/patch.go | 40 --------- api/app/role/create.go | 31 ------- api/app/role/del.go | 37 -------- api/app/role/get.go | 24 ------ api/app/role/init.go | 14 ---- api/app/role/list.go | 57 ------------- api/app/role/patch.go | 51 ----------- api/init.go | 27 ------ api/sms/init.go | 14 ---- api/sms/send.go | 105 ----------------------- api/sms/utils.go | 90 -------------------- api/sms/validate.go | 73 ---------------- api/token/base.go | 48 ----------- api/token/create.go | 133 ----------------------------- api/token/get.go | 48 ----------- api/token/init.go | 14 ---- api/user/create.go | 114 ------------------------- api/user/init.go | 20 ----- api/user/login.go | 114 ------------------------- api/user/role/create.go | 35 -------- api/user/role/del.go | 26 ------ api/user/role/get.go | 68 --------------- api/user/role/init.go | 12 --- api/user/role/patch.go | 36 -------- api/user/user.go | 129 ---------------------------- cfg/cfg.go | 39 ++------- cfg/dev.yml | 8 -- errs/errors.go | 18 ---- libs/auth/access.go | 68 --------------- libs/auth/jwt.go | 117 -------------------------- models/app.go | 106 ----------------------- models/init.go | 46 ---------- models/token.go | 22 ----- models/user.go | 43 ---------- 53 files changed, 8 insertions(+), 2601 deletions(-) delete mode 100644 api/app/access/create.go delete mode 100644 api/app/access/del.go delete mode 100644 api/app/access/get.go delete mode 100644 api/app/access/init.go delete mode 100644 api/app/access/list.go delete mode 100644 api/app/access/patch.go delete mode 100644 api/app/app.go delete mode 100644 api/app/app_user/create.go delete mode 100644 api/app/app_user/del.go delete mode 100644 api/app/app_user/get.go delete mode 100644 api/app/app_user/init.go delete mode 100644 api/app/app_user/list.go delete mode 100644 api/app/app_user/patch.go delete mode 100644 api/app/init.go delete mode 100644 api/app/resource/create.go delete mode 100644 api/app/resource/del.go delete mode 100644 api/app/resource/get.go delete mode 100644 api/app/resource/init.go delete mode 100644 api/app/resource/list.go delete mode 100644 api/app/resource/patch.go delete mode 100644 api/app/role/create.go delete mode 100644 api/app/role/del.go delete mode 100644 api/app/role/get.go delete mode 100644 api/app/role/init.go delete mode 100644 api/app/role/list.go delete mode 100644 api/app/role/patch.go delete mode 100644 api/sms/init.go delete mode 100644 api/sms/send.go delete mode 100644 api/sms/utils.go delete mode 100644 api/sms/validate.go delete mode 100644 api/token/base.go delete mode 100644 api/token/create.go delete mode 100644 api/token/get.go delete mode 100644 api/token/init.go delete mode 100644 api/user/create.go delete mode 100644 api/user/init.go delete mode 100644 api/user/login.go delete mode 100644 api/user/role/create.go delete mode 100644 api/user/role/del.go delete mode 100644 api/user/role/get.go delete mode 100644 api/user/role/init.go delete mode 100644 api/user/role/patch.go delete mode 100644 api/user/user.go delete mode 100644 cfg/dev.yml delete mode 100644 errs/errors.go delete mode 100644 libs/auth/access.go delete mode 100644 libs/auth/jwt.go delete mode 100644 models/app.go delete mode 100644 models/token.go delete mode 100644 models/user.go diff --git a/api/app/access/create.go b/api/app/access/create.go deleted file mode 100644 index 386753d..0000000 --- a/api/app/access/create.go +++ /dev/null @@ -1,45 +0,0 @@ -// -// create.go -// Copyright (C) 2024 veypi -// Distributed under terms of the MIT license. -// - -package access - -import ( - "github.com/veypi/vigo" - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" -) - -type createOpts struct { - AppID string `json:"app_id" src:"json" desc:"应用ID"` - UserID *string `json:"user_id" src:"json" desc:"用户ID"` - RoleID *string `json:"role_id" src:"json" desc:"角色ID"` - ResourceID *string `json:"resource_id" src:"json" desc:"资源ID"` - Name string `json:"name" src:"json" desc:"名称"` - TID string `json:"tid" src:"json" desc:"资源ID"` - Level uint `json:"level" src:"json" desc:"级别"` -} - -var _ = Router.Post("/", "创建访问权限", createAccess) - -func createAccess(x *vigo.X, opts *createOpts) (*models.Access, error) { - // 创建新记录 - access := models.Access{ - AppID: opts.AppID, - UserID: opts.UserID, - RoleID: opts.RoleID, - ResourceID: opts.ResourceID, - Name: opts.Name, - TID: opts.TID, - Level: opts.Level, - } - - // 保存到数据库 - if err := cfg.DB().Create(&access).Error; err != nil { - return nil, err - } - - return &access, nil -} diff --git a/api/app/access/del.go b/api/app/access/del.go deleted file mode 100644 index d1eccfe..0000000 --- a/api/app/access/del.go +++ /dev/null @@ -1,38 +0,0 @@ -// -// del.go -// Copyright (C) 2024 veypi -// Distributed under terms of the MIT license. -// - -package access - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type deleteOpts struct { - ID string `src:"path@id" desc:"记录ID"` -} - -var _ = Router.Delete("/{id}", "删除访问权限", deleteAccess) - -type deleteResponse struct { - Message string `json:"message"` -} - -func deleteAccess(x *vigo.X, opts *deleteOpts) (*deleteResponse, error) { - // 查找记录 - var access models.Access - if err := cfg.DB().Where("id = ?", opts.ID).First(&access).Error; err != nil { - return nil, vigo.NewError("未找到资源").WithCode(404) - } - - // 删除记录 - if err := cfg.DB().Delete(&access).Error; err != nil { - return nil, err - } - - return &deleteResponse{Message: "删除成功"}, nil -} diff --git a/api/app/access/get.go b/api/app/access/get.go deleted file mode 100644 index 915c015..0000000 --- a/api/app/access/get.go +++ /dev/null @@ -1,30 +0,0 @@ -// -// get.go -// Copyright (C) 2024 veypi -// Distributed under terms of the MIT license. -// - -package access - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type getIDReq struct { - ID string `src:"path@id" desc:"记录ID"` -} - -var _ = Router.Get("/{id}", "获取访问权限详情", getAccess) - -func getAccess(x *vigo.X, req *getIDReq) (*models.Access, error) { - // 查询数据库 - var access models.Access - err := cfg.DB().Where("id = ?", req.ID).First(&access).Error - if err != nil { - return nil, vigo.NewError("未找到资源").WithCode(404) - } - - return &access, nil -} diff --git a/api/app/access/init.go b/api/app/access/init.go deleted file mode 100644 index 3fae2d2..0000000 --- a/api/app/access/init.go +++ /dev/null @@ -1,14 +0,0 @@ -// -// Copyright (C) 2024 veypi -// 2024-09-23 16:28:13 -// Distributed under terms of the MIT license. -// -// Auto generated by OneBD. DO NOT EDIT - -package access - -import ( - "github.com/veypi/vigo" -) - -var Router = vigo.NewRouter() diff --git a/api/app/access/list.go b/api/app/access/list.go deleted file mode 100644 index 92ff1fa..0000000 --- a/api/app/access/list.go +++ /dev/null @@ -1,64 +0,0 @@ -// -// list.go -// Copyright (C) 2024 veypi -// Distributed under terms of the MIT license. -// - -package access - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type listOpts struct { - Page int `src:"query" desc:"页码" default:"1"` - PageSize int `src:"query" desc:"每页大小" default:"20"` - AppID string `src:"query" desc:"应用ID"` - UserID string `src:"query" desc:"用户ID"` - RoleID string `src:"query" desc:"角色ID"` - Name string `src:"query" desc:"名称"` -} - -type listResponse struct { - Total int64 `json:"total"` - Items []models.Access `json:"items"` -} - -var _ = Router.Get("/", "获取访问权限列表", listAccess) - -func listAccess(x *vigo.X, opts *listOpts) (*listResponse, error) { - // 构建查询 - db := cfg.DB().Model(&models.Access{}) - if opts.AppID != "" { - db = db.Where("app_id = ?", opts.AppID) - } - if opts.UserID != "" { - db = db.Where("user_id = ?", opts.UserID) - } - if opts.RoleID != "" { - db = db.Where("role_id = ?", opts.RoleID) - } - if opts.Name != "" { - db = db.Where("name LIKE ?", "%"+opts.Name+"%") - } - - // 计算总数 - var total int64 - if err := db.Count(&total).Error; err != nil { - return nil, err - } - - // 分页查询 - offset := (opts.Page - 1) * opts.PageSize - var items []models.Access - if err := db.Offset(offset).Limit(opts.PageSize).Find(&items).Error; err != nil { - return nil, err - } - - return &listResponse{ - Total: total, - Items: items, - }, nil -} diff --git a/api/app/access/patch.go b/api/app/access/patch.go deleted file mode 100644 index 0389771..0000000 --- a/api/app/access/patch.go +++ /dev/null @@ -1,56 +0,0 @@ -// -// patch.go -// Copyright (C) 2024 veypi -// Distributed under terms of the MIT license. -// - -package access - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type updateOpts struct { - ID string `src:"path@id" desc:"记录ID"` - Name *string `json:"name" src:"json" desc:"名称"` - TID *string `json:"tid" src:"json" desc:"资源ID"` - Level *uint `json:"level" src:"json" desc:"级别"` -} - -var _ = Router.Patch("/{id}", "更新访问权限", updateAccess) - -func updateAccess(x *vigo.X, opts *updateOpts) (*models.Access, error) { - // 查找记录 - var access models.Access - if err := cfg.DB().Where("id = ?", opts.ID).First(&access).Error; err != nil { - return nil, vigo.NewError("未找到资源").WithCode(404) - } - - // 更新字段 - updates := map[string]interface{}{} - if opts.Name != nil { - updates["name"] = *opts.Name - } - if opts.TID != nil { - updates["tid"] = *opts.TID - } - if opts.Level != nil { - updates["level"] = *opts.Level - } - - // 执行更新 - if len(updates) > 0 { - if err := cfg.DB().Model(&access).Updates(updates).Error; err != nil { - return nil, err - } - } - - // 返回更新后的记录 - if err := cfg.DB().Where("id = ?", opts.ID).First(&access).Error; err != nil { - return nil, err - } - - return &access, nil -} diff --git a/api/app/app.go b/api/app/app.go deleted file mode 100644 index eaf8396..0000000 --- a/api/app/app.go +++ /dev/null @@ -1,167 +0,0 @@ -package app - -import ( - "fmt" - "math/rand" - "time" - - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/libs/auth" - "github.com/veypi/vbase/libs/utils" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" - "gorm.io/gorm" -) - -type appIDReq struct { - AppID string `src:"path@app_id" desc:"应用ID"` -} - -var _ = Router.Get("/{app_id}/key", "重置应用密钥", auth.Check("app", "app_id", auth.DoDelete), appKey) - -func appKey(x *vigo.X, req *appIDReq) (string, error) { - data := &models.App{} - data.ID = req.AppID - key := utils.RandSeq(32) - err := cfg.DB().Model(data).Update("key", key).Error - - return key, err -} - -var _ = Router.Delete("/{app_id}", "删除应用", auth.Check("app", "app_id", auth.DoDelete), appDelete) - -func appDelete(x *vigo.X, req *appIDReq) (*models.App, error) { - data := &models.App{} - - err := cfg.DB().Where("id = ?", req.AppID).Delete(data).Error - - return data, err -} - -var _ = Router.Get("/{app_id}", "获取应用详情", auth.Check("app", "app_id", auth.DoRead), appGet) - -func appGet(x *vigo.X, req *appIDReq) (*models.App, error) { - data := &models.App{} - err := cfg.DB().Where("id = ?", req.AppID).First(data).Error - - return data, err -} - -type listOpts struct { - Name *string `json:"name" src:"query" desc:"应用名称"` -} - -var _ = Router.Get("/", "获取应用列表", appList) - -type appListItem struct { - models.App - UserStatus string `json:"user_status"` -} - -func appList(x *vigo.X, opts *listOpts) ([]*appListItem, error) { - data := make([]*appListItem, 0, 10) - token, err := auth.CheckJWT(x) - if err == nil { - uid := token.UID - query := cfg.DB().Table("apps").Select("apps.*,app_users.status user_status") - if opts.Name != nil { - query = query.Joins("LEFT JOIN app_users ON app_users.app_id = apps.id AND app_users.user_id = ? AND apps.name LIKE ?", uid, opts.Name) - } else { - query = query.Joins("LEFT JOIN app_users ON app_users.app_id = apps.id AND app_users.user_id = ?", uid) - } - err = query.Scan(&data).Error - } else { - err = cfg.DB().Table("apps").Select("id", "name", "icon").Find(&data).Error - } - return data, err -} - -type postOpts struct { - Name string `json:"name" src:"json" desc:"应用名称"` - Icon string `json:"icon" src:"json" desc:"图标"` - Des *string `json:"des" src:"json" desc:"描述"` - Typ string `json:"typ" src:"json" desc:"类型"` - Status string `json:"status" src:"json" desc:"状态"` - InitUrl string `json:"init_url" src:"json" desc:"初始化URL"` -} - -var _ = Router.Post("/", "创建应用", auth.Check("app", "", auth.DoCreate), appPost) - -func appPost(x *vigo.X, opts *postOpts) (*models.App, error) { - data := &models.App{} - data.Name = opts.Name - data.Icon = opts.Icon - if data.Icon == "" { - data.Icon = fmt.Sprintf("https://public.veypi.com/img/avatar/%04d.jpg", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(220)) - } - data.Des = "" - if opts.Des != nil { - data.Des = *opts.Des - } - data.Typ = opts.Typ - data.Status = opts.Status - data.InitUrl = opts.InitUrl - err := cfg.DB().Transaction(func(tx *gorm.DB) error { - data.Key = utils.RandSeq(32) - err := tx.Create(data).Error - if err != nil { - return err - } - uid, _ := x.Get("uid").(string) - au := &models.AppUser{ - AppID: data.ID, - UserID: uid, - Status: models.AUSTATUS_OK, - } - return tx.Create(au).Error - }) - - return data, err -} - -type patchOpts struct { - ID string `json:"id" gorm:"primaryKey;type:varchar(36)" src:"path@app_id" desc:"应用ID"` - Name *string `json:"name" src:"json" desc:"应用名称"` - Icon *string `json:"icon" src:"json" desc:"图标"` - Des *string `json:"des" src:"json" desc:"描述"` - Typ *string `json:"typ" gorm:"default:auto" src:"json" desc:"类型"` - InitRoleID *string `json:"init_role_id" gorm:"index;type:varchar(36)" src:"json" desc:"初始角色ID"` - Status *string `json:"status" gorm:"default:ok" src:"json" desc:"状态"` - InitUrl *string `json:"init_url" src:"json" desc:"初始化URL"` -} - -var _ = Router.Patch("/{app_id}", "更新应用", auth.Check("app", "app_id", auth.DoUpdate), appPatch) - -func appPatch(x *vigo.X, opts *patchOpts) (*models.App, error) { - data := &models.App{} - - err := cfg.DB().Where("id = ?", opts.ID).First(data).Error - if err != nil { - return nil, err - } - optsMap := make(map[string]interface{}) - if opts.Name != nil { - optsMap["name"] = opts.Name - } - if opts.Icon != nil { - optsMap["icon"] = opts.Icon - } - if opts.Des != nil { - optsMap["des"] = opts.Des - } - if opts.Typ != nil { - optsMap["typ"] = opts.Typ - } - if opts.InitRoleID != nil { - optsMap["init_role_id"] = opts.InitRoleID - } - if opts.Status != nil { - optsMap["status"] = opts.Status - } - if opts.InitUrl != nil { - optsMap["init_url"] = opts.InitUrl - } - err = cfg.DB().Model(data).Updates(optsMap).Error - - return data, err -} diff --git a/api/app/app_user/create.go b/api/app/app_user/create.go deleted file mode 100644 index 34c2c3c..0000000 --- a/api/app/app_user/create.go +++ /dev/null @@ -1,29 +0,0 @@ -package app_user - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type createOpts struct { - AppID string `src:"path@app_id" desc:"应用ID"` - UserID string `json:"user_id" src:"json" desc:"用户ID"` - Status string `json:"status" src:"json" desc:"状态" default:"ok"` -} - -var _ = Router.Post("/", "创建应用用户", createAppUser) - -func createAppUser(x *vigo.X, opts *createOpts) (*models.AppUser, error) { - appUser := &models.AppUser{ - AppID: opts.AppID, - UserID: opts.UserID, - Status: opts.Status, - } - - if err := cfg.DB().Create(appUser).Error; err != nil { - return nil, err - } - - return appUser, nil -} diff --git a/api/app/app_user/del.go b/api/app/app_user/del.go deleted file mode 100644 index 91eb134..0000000 --- a/api/app/app_user/del.go +++ /dev/null @@ -1,37 +0,0 @@ -package app_user - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type deleteOpts struct { - AppID string `src:"path@app_id" desc:"应用ID"` - UserID string `src:"path@user_id" desc:"用户ID"` -} - -var _ = Router.Delete("/{user_id}", "删除应用用户", deleteAppUser) - -type deleteAppUserResponse struct { - Message string `json:"message"` - AppID string `json:"app_id"` - UserID string `json:"user_id"` -} - -func deleteAppUser(x *vigo.X, opts *deleteOpts) (*deleteAppUserResponse, error) { - appUser := &models.AppUser{} - if err := cfg.DB().Where("app_id = ? AND user_id = ?", opts.AppID, opts.UserID).First(appUser).Error; err != nil { - return nil, vigo.NewError("app_user not found").WithCode(404) - } - - if err := cfg.DB().Delete(appUser).Error; err != nil { - return nil, err - } - - return &deleteAppUserResponse{ - Message: "app_user deleted successfully", - AppID: opts.AppID, - UserID: opts.UserID, - }, nil -} diff --git a/api/app/app_user/get.go b/api/app/app_user/get.go deleted file mode 100644 index 2cbe771..0000000 --- a/api/app/app_user/get.go +++ /dev/null @@ -1,24 +0,0 @@ -package app_user - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type appUserIDReq struct { - AppID string `src:"path@app_id" desc:"应用ID"` - UserID string `src:"path@user_id" desc:"用户ID"` -} - -var _ = Router.Get("/{user_id}", "获取应用用户详情", getAppUser) - -func getAppUser(x *vigo.X, req *appUserIDReq) (*models.AppUser, error) { - appUser := &models.AppUser{} - err := cfg.DB().Where("app_id = ? AND user_id = ?", req.AppID, req.UserID).First(appUser).Error - if err != nil { - return nil, vigo.NewError("app_user not found").WithCode(404) - } - - return appUser, nil -} diff --git a/api/app/app_user/init.go b/api/app/app_user/init.go deleted file mode 100644 index f80573f..0000000 --- a/api/app/app_user/init.go +++ /dev/null @@ -1,14 +0,0 @@ -// -// init.go -// Copyright (C) 2025 veypi -// 2025-05-13 16:47 -// Distributed under terms of the MIT license. -// - -package app_user - -import ( - "github.com/veypi/vigo" -) - -var Router = vigo.NewRouter() diff --git a/api/app/app_user/list.go b/api/app/app_user/list.go deleted file mode 100644 index 36f8c52..0000000 --- a/api/app/app_user/list.go +++ /dev/null @@ -1,47 +0,0 @@ -package app_user - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type listOpts struct { - AppID string `src:"path@app_id" desc:"应用ID"` - Page int `src:"query" desc:"页码" default:"1"` - PageSize int `src:"query" desc:"每页数量" default:"20"` - Status string `src:"query" desc:"状态" default:""` -} - -type listResponse struct { - Total int64 `json:"total"` - Items []*models.AppUser `json:"items"` -} - -var _ = Router.Get("/", "获取应用用户列表", listAppUsers) - -func listAppUsers(x *vigo.X, opts *listOpts) (*listResponse, error) { - query := cfg.DB().Model(&models.AppUser{}).Where("app_id = ?", opts.AppID) - - if opts.Status != "" { - query = query.Where("status = ?", opts.Status) - } - - var total int64 - if err := query.Count(&total).Error; err != nil { - return nil, err - } - - offset := (opts.Page - 1) * opts.PageSize - query = query.Offset(offset).Limit(opts.PageSize) - - var users []*models.AppUser - if err := query.Find(&users).Error; err != nil { - return nil, err - } - - return &listResponse{ - Total: total, - Items: users, - }, nil -} diff --git a/api/app/app_user/patch.go b/api/app/app_user/patch.go deleted file mode 100644 index cfcd804..0000000 --- a/api/app/app_user/patch.go +++ /dev/null @@ -1,35 +0,0 @@ -package app_user - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type updateOpts struct { - AppID string `src:"path@app_id" desc:"应用ID"` - UserID string `src:"path@user_id" desc:"用户ID"` - Status *string `json:"status" src:"json" desc:"状态"` -} - -var _ = Router.Patch("/{user_id}", "更新应用用户", updateAppUser) - -func updateAppUser(x *vigo.X, opts *updateOpts) (*models.AppUser, error) { - appUser := &models.AppUser{} - if err := cfg.DB().Where("app_id = ? AND user_id = ?", opts.AppID, opts.UserID).First(appUser).Error; err != nil { - return nil, vigo.NewError("app_user not found").WithCode(404) - } - - updates := map[string]interface{}{} - if opts.Status != nil { - updates["status"] = *opts.Status - } - - if len(updates) > 0 { - if err := cfg.DB().Model(appUser).Updates(updates).Error; err != nil { - return nil, err - } - } - - return appUser, nil -} diff --git a/api/app/init.go b/api/app/init.go deleted file mode 100644 index e09425c..0000000 --- a/api/app/init.go +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (C) 2024 veypi -// 2024-09-23 16:28:13 -// Distributed under terms of the MIT license. -// -// Auto generated by OneBD. DO NOT EDIT - -package app - -import ( - "github.com/veypi/vbase/api/app/access" - "github.com/veypi/vbase/api/app/app_user" - "github.com/veypi/vbase/api/app/resource" - "github.com/veypi/vbase/api/app/role" - "github.com/veypi/vbase/libs/auth" - "github.com/veypi/vigo" -) - -var Router = vigo.NewRouter() -var appRouter = Router.SubRouter("/{app_id}").Use(auth.Check("app", "{app_id}", 2)) - -func init() { - appRouter.Extend("/resource", resource.Router) - appRouter.Extend("/user", app_user.Router) - appRouter.Extend("/role", role.Router) - appRouter.Extend("/access", access.Router) -} diff --git a/api/app/resource/create.go b/api/app/resource/create.go deleted file mode 100644 index 62b912b..0000000 --- a/api/app/resource/create.go +++ /dev/null @@ -1,31 +0,0 @@ -package resource - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type createOpts struct { - AppID string `json:"app_id" src:"path@app_id" desc:"应用ID"` - Name string `json:"name" src:"json" desc:"资源名称"` - Des string `json:"des" src:"json" desc:"资源描述"` -} - -var _ = Router.Post("/", "创建资源", createResource) - -func createResource(x *vigo.X, opts *createOpts) (*models.Resource, error) { - // 创建资源对象 - resource := &models.Resource{ - AppID: opts.AppID, - Name: opts.Name, - Des: opts.Des, - } - - // 保存到数据库 - if err := cfg.DB().Create(resource).Error; err != nil { - return nil, vigo.NewError("failed to create resource").WithCode(500) - } - - return resource, nil -} diff --git a/api/app/resource/del.go b/api/app/resource/del.go deleted file mode 100644 index 25f15d9..0000000 --- a/api/app/resource/del.go +++ /dev/null @@ -1,36 +0,0 @@ -package resource - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type deleteOpts struct { - ResourceID string `src:"path@resource_id" desc:"资源ID"` -} - -var _ = Router.Delete("/{resource_id}", "删除资源", deleteResource) - -type deleteResourceResponse struct { - Message string `json:"message"` - ID string `json:"id"` -} - -func deleteResource(x *vigo.X, opts *deleteOpts) (*deleteResourceResponse, error) { - // 查找资源 - resource := &models.Resource{} - if err := cfg.DB().Where("id = ?", opts.ResourceID).First(resource).Error; err != nil { - return nil, vigo.NewError("resource not found").WithCode(404) - } - - // 删除资源 - if err := cfg.DB().Delete(resource).Error; err != nil { - return nil, vigo.NewError("failed to delete resource").WithCode(500) - } - - return &deleteResourceResponse{ - Message: "resource deleted successfully", - ID: opts.ResourceID, - }, nil -} diff --git a/api/app/resource/get.go b/api/app/resource/get.go deleted file mode 100644 index 0da22a7..0000000 --- a/api/app/resource/get.go +++ /dev/null @@ -1,24 +0,0 @@ -package resource - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type getIDReq struct { - ID string `src:"path@resource_id" desc:"资源ID"` -} - -var _ = Router.Get("/{resource_id}", "获取资源详情", getResource) - -func getResource(x *vigo.X, req *getIDReq) (*models.Resource, error) { - // 查询数据库 - resource := &models.Resource{} - err := cfg.DB().Where("id = ?", req.ID).First(resource).Error - if err != nil { - return nil, vigo.NewError("resource not found").WithCode(404) - } - - return resource, nil -} diff --git a/api/app/resource/init.go b/api/app/resource/init.go deleted file mode 100644 index 7e5104a..0000000 --- a/api/app/resource/init.go +++ /dev/null @@ -1,14 +0,0 @@ -// -// init.go -// Copyright (C) 2025 veypi -// 2025-05-13 16:45 -// Distributed under terms of the MIT license. -// - -package resource - -import ( - "github.com/veypi/vigo" -) - -var Router = vigo.NewRouter() diff --git a/api/app/resource/list.go b/api/app/resource/list.go deleted file mode 100644 index 005e2ec..0000000 --- a/api/app/resource/list.go +++ /dev/null @@ -1,50 +0,0 @@ -package resource - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type listOpts struct { - Page int `src:"query" desc:"页码" default:"1"` - PageSize int `src:"query" desc:"每页数量" default:"20"` - AppID string `src:"query" desc:"应用ID"` - Name string `src:"query" desc:"资源名称"` -} - -type listResponse struct { - Total int64 `json:"total"` - Items []*models.Resource `json:"items"` -} - -var _ = Router.Get("/", "获取资源列表", listResources) - -func listResources(x *vigo.X, opts *listOpts) (*listResponse, error) { - // 构建查询 - db := cfg.DB().Model(&models.Resource{}) - if opts.AppID != "" { - db = db.Where("app_id = ?", opts.AppID) - } - if opts.Name != "" { - db = db.Where("name LIKE ?", "%"+opts.Name+"%") - } - - // 计算总数 - var total int64 - if err := db.Count(&total).Error; err != nil { - return nil, err - } - - // 分页查询 - offset := (opts.Page - 1) * opts.PageSize - var resources []*models.Resource - if err := db.Offset(offset).Limit(opts.PageSize).Find(&resources).Error; err != nil { - return nil, err - } - - return &listResponse{ - Total: total, - Items: resources, - }, nil -} diff --git a/api/app/resource/patch.go b/api/app/resource/patch.go deleted file mode 100644 index 3279e8f..0000000 --- a/api/app/resource/patch.go +++ /dev/null @@ -1,40 +0,0 @@ -package resource - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type updateOpts struct { - ResourceID string `src:"path@resource_id" desc:"资源ID"` - Name *string `json:"name" src:"json" desc:"资源名称"` - Des *string `json:"des" src:"json" desc:"资源描述"` -} - -var _ = Router.Patch("/{resource_id}", "更新资源", updateResource) - -func updateResource(x *vigo.X, opts *updateOpts) (*models.Resource, error) { - // 查找资源 - resource := &models.Resource{} - if err := cfg.DB().Where("id = ?", opts.ResourceID).First(resource).Error; err != nil { - return nil, vigo.NewError("resource not found").WithCode(404) - } - - // 更新字段 - updates := map[string]interface{}{} - if opts.Name != nil { - updates["name"] = *opts.Name - } - if opts.Des != nil { - updates["des"] = *opts.Des - } - - // 执行更新 - if len(updates) > 0 { - if err := cfg.DB().Model(resource).Updates(updates).Error; err != nil { - return nil, vigo.NewError("failed to update resource").WithCode(500) - } - } - return resource, nil -} diff --git a/api/app/role/create.go b/api/app/role/create.go deleted file mode 100644 index 1126a4c..0000000 --- a/api/app/role/create.go +++ /dev/null @@ -1,31 +0,0 @@ -package role - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type createOpts struct { - AppID string `src:"path@app_id" desc:"应用ID"` // 应用 ID - Name string `json:"name" src:"json" desc:"角色名称"` // 角色名称 - Des string `json:"des" src:"json" desc:"角色描述"` // 角色描述 -} - -var _ = Router.Post("/", "创建角色", createRole) - -func createRole(x *vigo.X, opts *createOpts) (*models.Role, error) { - // 创建角色 - role := &models.Role{ - AppID: opts.AppID, - Name: opts.Name, - Des: opts.Des, - } - - // 保存到数据库 - if err := cfg.DB().Create(role).Error; err != nil { - return nil, err - } - - return role, nil -} diff --git a/api/app/role/del.go b/api/app/role/del.go deleted file mode 100644 index c155160..0000000 --- a/api/app/role/del.go +++ /dev/null @@ -1,37 +0,0 @@ -package role - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type deleteOpts struct { - RoleID string `src:"path@role_id" desc:"角色ID"` // 角色 ID -} - -var _ = Router.Delete("/{role_id}", "删除角色", deleteRole) - -type deleteRoleResponse struct { - Message string `json:"message"` - ID string `json:"id"` -} - -func deleteRole(x *vigo.X, opts *deleteOpts) (*deleteRoleResponse, error) { - // 查找角色 - role := &models.Role{} - if err := cfg.DB().Where("id = ?", opts.RoleID).First(role).Error; err != nil { - return nil, vigo.NewError("role not found").WithCode(404) - } - - // 执行删除操作 - if err := cfg.DB().Delete(role).Error; err != nil { - return nil, err - } - - // 返回成功消息 - return &deleteRoleResponse{ - Message: "role deleted successfully", - ID: opts.RoleID, - }, nil -} diff --git a/api/app/role/get.go b/api/app/role/get.go deleted file mode 100644 index ebd5430..0000000 --- a/api/app/role/get.go +++ /dev/null @@ -1,24 +0,0 @@ -package role - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type getIDReq struct { - ID string `src:"path@role_id" desc:"角色ID"` -} - -var _ = Router.Get("/{role_id}", "获取角色详情", getRole) - -func getRole(x *vigo.X, req *getIDReq) (*models.Role, error) { - // 查询数据库 - role := &models.Role{} - err := cfg.DB().Where("id = ?", req.ID).First(role).Error - if err != nil { - return nil, vigo.NewError("role not found").WithCode(404) - } - - return role, nil -} diff --git a/api/app/role/init.go b/api/app/role/init.go deleted file mode 100644 index 7a7baa5..0000000 --- a/api/app/role/init.go +++ /dev/null @@ -1,14 +0,0 @@ -// -// init.go -// Copyright (C) 2025 veypi -// 2025-05-13 16:49 -// Distributed under terms of the MIT license. -// - -package role - -import ( - "github.com/veypi/vigo" -) - -var Router = vigo.NewRouter() diff --git a/api/app/role/list.go b/api/app/role/list.go deleted file mode 100644 index 2074849..0000000 --- a/api/app/role/list.go +++ /dev/null @@ -1,57 +0,0 @@ -package role - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -var _ = Router.Get("/", "获取角色列表", listRoles) - -type listOpts struct { - AppID string `src:"path@app_id" desc:"应用ID"` - Page int `src:"query" default:"1" desc:"页码"` - PageSize int `src:"query" default:"20" desc:"页大小"` - Keyword string `src:"query" default:"" desc:"关键词"` - SortBy string `src:"query" default:"created_at" desc:"排序字段"` - Order string `src:"query" default:"desc" desc:"排序方向"` -} - -type listResponse struct { - Total int64 `json:"total"` - Items []*models.Role `json:"items"` -} - -func listRoles(x *vigo.X, opts *listOpts) (*listResponse, error) { - // 构建查询 - db := cfg.DB().Model(&models.Role{}).Where("app_id = ?", opts.AppID) - query := db - - // 应用关键词搜索 - if opts.Keyword != "" { - query = query.Where("name LIKE ? OR des LIKE ?", "%"+opts.Keyword+"%", "%"+opts.Keyword+"%") - } - - // 计算总数 - var total int64 - if err := query.Count(&total).Error; err != nil { - return nil, err - } - - // 分页和排序 - offset := (opts.Page - 1) * opts.PageSize - query = query.Order(opts.SortBy + " " + opts.Order) - query = query.Offset(offset).Limit(opts.PageSize) - - // 查询数据 - var roles []*models.Role - if err := query.Find(&roles).Error; err != nil { - return nil, err - } - - // 返回结果 - return &listResponse{ - Total: total, - Items: roles, - }, nil -} diff --git a/api/app/role/patch.go b/api/app/role/patch.go deleted file mode 100644 index 00275c9..0000000 --- a/api/app/role/patch.go +++ /dev/null @@ -1,51 +0,0 @@ -package role - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type updateOpts struct { - RoleID string `src:"path@role_id" desc:"角色ID"` // 角色 ID - Name *string `json:"name" src:"json" desc:"角色名称"` // 可选,角色名称 - Des *string `json:"des" src:"json" desc:"角色描述"` // 可选,角色描述 -} - -var _ = Router.Patch("/{role_id}", "更新角色", updateRole) - -func updateRole(x *vigo.X, opts *updateOpts) (*models.Role, error) { - // 查找角色 - role := &models.Role{} - if err := cfg.DB().Where("id = ?", opts.RoleID).First(role).Error; err != nil { - return nil, vigo.NewError("role not found").WithCode(404) - } - - // 准备更新字段 - updates := map[string]interface{}{} - - if opts.Name != nil { - updates["name"] = *opts.Name - } - - if opts.Des != nil { - updates["des"] = *opts.Des - } - - // 如果没有更新字段,直接返回 - if len(updates) == 0 { - return role, nil - } - - // 执行更新 - if err := cfg.DB().Model(role).Updates(updates).Error; err != nil { - return nil, err - } - - // 重新查询以获取最新数据 - if err := cfg.DB().Where("id = ?", opts.RoleID).First(role).Error; err != nil { - return nil, err - } - - return role, nil -} diff --git a/api/init.go b/api/init.go index 03689b9..87dd567 100644 --- a/api/init.go +++ b/api/init.go @@ -8,12 +8,6 @@ package api import ( - "github.com/veypi/vbase/api/app" - "github.com/veypi/vbase/api/sms" - "github.com/veypi/vbase/api/token" - "github.com/veypi/vbase/api/user" - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/libs/auth" "github.com/veypi/vigo" "github.com/veypi/vigo/contrib/common" ) @@ -22,31 +16,10 @@ var Router = vigo.NewRouter() func init() { // 注册全局中间件 - Router.Use(func(x *vigo.X) (any, error) { return auth.CheckJWT(x) }) Router.After(common.JsonResponse, common.JsonErrorResponse) - // 注册子资源路由 - Router.Extend("user", user.Router) - Router.Extend("token", token.Router) - Router.Extend("app", app.Router) - Router.Extend("sms", sms.Router) - // Router.Extend("oauth", oauth.Router) - - // 注册基础接口 - Router.Get("/cfg", "获取配置信息", vigo.SkipBefore, getCfg) - // 404 处理 Router.Any("/**", vigo.SkipBefore, "拦截未注册的api请求,返回404", func(x *vigo.X) error { return vigo.ErrNotFound }) } - -type CfgResponse struct { - SMS bool `json:"sms" desc:"是否启用短信服务"` -} - -func getCfg(x *vigo.X) *CfgResponse { - return &CfgResponse{ - SMS: cfg.Config.SMS.Enable, - } -} diff --git a/api/sms/init.go b/api/sms/init.go deleted file mode 100644 index 205475a..0000000 --- a/api/sms/init.go +++ /dev/null @@ -1,14 +0,0 @@ -// -// init.go -// Copyright (C) 2025 veypi -// -// Distributed under terms of the MIT license. -// - -package sms - -import "github.com/veypi/vigo" - -var Router = vigo.NewRouter() - - diff --git a/api/sms/send.go b/api/sms/send.go deleted file mode 100644 index 361b37e..0000000 --- a/api/sms/send.go +++ /dev/null @@ -1,105 +0,0 @@ -package sms - -import ( - "fmt" - "time" - - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/libs/sms_providers" - "github.com/veypi/vbase/libs/utils" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" - "github.com/veypi/vigo/contrib/limiter" - "gorm.io/gorm" -) - -// SendCodeRequest 发送验证码请求 -type SendCodeRequest struct { - Phone string `json:"phone" src:"json" desc:"手机号"` - Region string `json:"region" src:"json" desc:"区域"` - Purpose string `json:"purpose" src:"json" desc:"用途"` - TemplateID string `json:"template_id" src:"json" desc:"模板ID"` -} - -// SendCodeResponse 发送验证码响应 -type SendCodeResponse struct { - ID string `json:"id"` - Phone string `json:"phone"` - ExpiresAt time.Time `json:"expires_at"` - Interval int `json:"interval"` // 下次可发送间隔(秒) -} - -var _ = Router.Post("/", "发送验证码", vigo.SkipBefore, limiter.NewAdvancedRequestLimiter(time.Minute*3, 5, time.Second*30).Limit, sendCode) - -// SendCode 发送验证码 -func sendCode(x *vigo.X, req *SendCodeRequest) (*SendCodeResponse, error) { - // 1. 验证手机号 - normalizedPhone := utils.NormalizePhoneNumber(req.Phone) - if !utils.ValidatePhoneNumber(normalizedPhone) { - return nil, fmt.Errorf("invalid phone number: %s", req.Phone) - } - - // 2. 检查区域配置 - provider, err := sms_providers.GetSMSProvider(req.Region) - if err != nil { - return nil, err - } - - // 3. 检查发送频率限制 - if err := CheckSendInterval(normalizedPhone, req.Region, req.Purpose); err != nil { - return nil, err - } - - // 4. 检查每日发送次数限制 - if err := CheckDailyLimit(normalizedPhone, req.Region); err != nil { - return nil, err - } - - // 5. 生成验证码 - code, err := utils.GenerateCode(cfg.Config.SMS.Global.CodeLength) - if err != nil { - return nil, fmt.Errorf("failed to generate code: %w", err) - } - - // 6. 创建验证码记录 - smsCode := &models.SMSCode{ - Phone: normalizedPhone, - Code: code, - Region: req.Region, - Purpose: req.Purpose, - Status: models.CodeStatusPending, - ExpiresAt: time.Now().Add(cfg.Config.SMS.Global.CodeExpiry), - } - - // 7. 发送短信 - sendReq := &sms_providers.SendSMSRequest{ - Phone: normalizedPhone, - TemplateID: req.TemplateID, - Region: req.Region, - Purpose: req.Purpose, - Params: map[string]string{ - "code": code, - }, - } - err = cfg.DB().Transaction(func(tx *gorm.DB) error { - err := tx.Create(smsCode).Error - if err != nil { - return err - } - smsID, err := provider.SendSMS(x.Context(), sendReq) - LogSMS(normalizedPhone, req.Region, provider.GetName(), smsID, err) - if err != nil || smsID == "" { - return fmt.Errorf("failed to send sms: %v", err) - } - return nil - }) - if err != nil { - return nil, err - } - return &SendCodeResponse{ - ID: smsCode.ID, - Phone: smsCode.Phone, - ExpiresAt: smsCode.ExpiresAt, - Interval: int(cfg.Config.SMS.Global.SendInterval.Seconds()), - }, nil -} diff --git a/api/sms/utils.go b/api/sms/utils.go deleted file mode 100644 index ee5bd29..0000000 --- a/api/sms/utils.go +++ /dev/null @@ -1,90 +0,0 @@ -// -// utils.go -// Copyright (C) 2025 veypi -// -// Distributed under terms of the MIT license. -// - -package sms - -import ( - "fmt" - "time" - - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "gorm.io/gorm" -) - -// checkDailyLimit 检查每日发送次数限制 -func CheckDailyLimit(phone, region string) error { - today := time.Now().Truncate(24 * time.Hour) - tomorrow := today.Add(24 * time.Hour) - - var count int64 - err := cfg.DB().Model(&models.SMSCode{}). - Where("phone = ? AND region = ? AND created_at >= ? AND created_at < ?", - phone, region, today, tomorrow). - Count(&count).Error - - if err != nil { - return fmt.Errorf("failed to check daily limit: %w", err) - } - - if count >= int64(cfg.Config.SMS.Global.MaxDailyCount) { - return fmt.Errorf("daily sms limit exceeded") - } - - return nil -} - -// checkSendInterval 检查发送间隔 -func CheckSendInterval(phone, region, purpose string) error { - var lastCode models.SMSCode - err := cfg.DB().Where("phone = ? AND region = ? AND purpose = ?", phone, region, purpose). - Order("created_at DESC"). - First(&lastCode).Error - - if err != nil && err != gorm.ErrRecordNotFound { - return fmt.Errorf("failed to check send interval: %w", err) - } - - if err == nil { - timeSinceLastSend := time.Since(lastCode.CreatedAt) - if timeSinceLastSend < cfg.Config.SMS.Global.SendInterval { - remaining := cfg.Config.SMS.Global.SendInterval - timeSinceLastSend - return fmt.Errorf("please wait %d seconds before sending again", int(remaining.Seconds())) - } - } - - return nil -} - -// logSMS 记录短信发送日志 -func LogSMS(phone, region, provider, messageID string, err error) { - log := &models.SMSLog{ - Phone: phone, - Region: region, - Provider: provider, - MessageID: messageID, - Status: "ok", - } - if err != nil { - log.Status = "failed" - log.Error = err.Error() - } - cfg.DB().Create(log) -} - -// CleanupExpiredCodes 清理过期的验证码 -func CleanupExpiredCodes() error { - result := cfg.DB().Model(&models.SMSCode{}). - Where("status = ? AND expires_at < ?", models.CodeStatusPending, time.Now()). - Update("status", models.CodeStatusExpired) - - if result.Error != nil { - return fmt.Errorf("failed to cleanup expired codes: %w", result.Error) - } - - return nil -} diff --git a/api/sms/validate.go b/api/sms/validate.go deleted file mode 100644 index fd12053..0000000 --- a/api/sms/validate.go +++ /dev/null @@ -1,73 +0,0 @@ -// -// validate.go -// Copyright (C) 2025 veypi -// -// Distributed under terms of the MIT license. -// - -package sms - -import ( - "fmt" - "time" - - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/libs/utils" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" - "gorm.io/gorm" -) - -// VerifyCode 验证验证码 -func VerifyCode(Phone, Code, Region, Purpose string) error { - normalizedPhone := utils.NormalizePhoneNumber(Phone) - - // 1. 查找最新的待验证码 - var smsCode models.SMSCode - err := cfg.DB().Where("phone = ? AND region = ? AND purpose = ? AND status = ?", - normalizedPhone, Region, Purpose, models.CodeStatusPending). - Order("created_at DESC"). - First(&smsCode).Error - - if err != nil { - if err == gorm.ErrRecordNotFound { - return vigo.NewError("verification code not found or already used").WithCode(404) - } - return vigo.NewError("failed to query sms code").WithError(err) - } - - // 2. 检查验证码是否过期 - if smsCode.IsExpired() { - cfg.DB().Model(&smsCode).Updates(map[string]any{ - "status": models.CodeStatusExpired, - }) - return fmt.Errorf("verification code has expired") - } - - // 3. 检查是否已达到最大尝试次数 - if !smsCode.CanRetry(cfg.Config.SMS.Global.MaxAttempts) { - cfg.DB().Model(&smsCode).Updates(map[string]any{ - "status": models.CodeStatusFailed, - }) - return fmt.Errorf("verification failed too many times") - } - - // 4. 验证码不匹配 - if smsCode.Code != Code { - cfg.DB().Model(&smsCode).Updates(map[string]any{ - "attempts": smsCode.Attempts + 1, - }) - - remaining := cfg.Config.SMS.Global.MaxAttempts - smsCode.Attempts - 1 - return fmt.Errorf("verification code incorrect, %d attempts remaining", remaining) - } - - // 5. 验证成功 - now := time.Now() - cfg.DB().Model(&smsCode).Updates(map[string]any{ - "status": models.CodeStatusUsed, - "used_at": &now, - }) - - return nil -} diff --git a/api/token/base.go b/api/token/base.go deleted file mode 100644 index 584ed65..0000000 --- a/api/token/base.go +++ /dev/null @@ -1,48 +0,0 @@ -package token - -import ( - "time" - - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type patchOpts struct { - ID string `json:"id" src:"path@token_id" desc:"令牌ID"` - ExpiredAt *time.Time `json:"expired_at" src:"json" desc:"过期时间"` - OverPerm *string `json:"over_perm" src:"json" desc:"覆盖权限"` -} - -var _ = Router.Patch("/{token_id}", "更新 Token", tokenPatch) - -func tokenPatch(x *vigo.X, opts *patchOpts) (*models.Token, error) { - data := &models.Token{} - - err := cfg.DB().Where("id = ?", opts.ID).First(data).Error - if err != nil { - return nil, err - } - optsMap := make(map[string]interface{}) - if opts.ExpiredAt != nil { - optsMap["expired_at"] = opts.ExpiredAt - } - if opts.OverPerm != nil { - optsMap["over_perm"] = opts.OverPerm - } - err = cfg.DB().Model(data).Updates(optsMap).Error - - return data, err -} - -type deleteOpts struct { - ID string `src:"path@token_id" desc:"令牌ID"` -} - -var _ = Router.Delete("/{token_id}", "删除 Token", tokenDelete) - -func tokenDelete(x *vigo.X, opts *deleteOpts) (*models.Token, error) { - data := &models.Token{} - err := cfg.DB().Where("id = ?", opts.ID).Delete(data).Error - return data, err -} diff --git a/api/token/create.go b/api/token/create.go deleted file mode 100644 index d9ef334..0000000 --- a/api/token/create.go +++ /dev/null @@ -1,133 +0,0 @@ -// -// post.go -// Copyright (C) 2025 veypi -// 2025-05-09 17:26 -// Distributed under terms of the MIT license. -// - -package token - -import ( - "net/http" - "time" - - "github.com/golang-jwt/jwt/v5" - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/libs/auth" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" - "github.com/veypi/vigo/logv" -) - -type postOpts struct { - // 两种获取token方式,一种用refreshtoken换取apptoken(应用登录),一种用密码加密code换refreshtoken (oa登录) - Refresh *string `json:"refresh" src:"json" desc:"刷新令牌"` - Typ *string `json:"typ" src:"json" desc:"令牌类型"` - - AppID *string `json:"app_id" gorm:"index;type:varchar(36)" src:"json" desc:"应用ID"` - ExpiredAt *time.Time `json:"expired_at" src:"json" desc:"过期时间"` - OverPerm *string `json:"over_perm" src:"json" desc:"覆盖权限"` - Device *string `json:"device" src:"json" desc:"设备信息"` -} - -var _ = Router.Post("/", "创建 Token", vigo.SkipBefore, tokenPost) - -// for user login app -func tokenPost(x *vigo.X, opts *postOpts) (string, error) { - aid := cfg.Config.ID - if opts.AppID != nil && *opts.AppID != "" { - aid = *opts.AppID - } - data := &models.Token{} - claim := &auth.Claims{} - claim.IssuedAt = jwt.NewNumericDate(time.Now()) - claim.Issuer = cfg.Config.ID - if opts.Refresh != nil { - typ := "app" - if opts.Typ != nil { - typ = *opts.Typ - } - // for other app redirect - refresh, err := auth.ParseJwt(*opts.Refresh) - if err != nil { - return "", err - } - if refresh.ID == "" { - return "", vigo.ErrNotAuthorized - } - err = cfg.DB().Where("id = ?", refresh.ID).First(data).Error - if err != nil { - return "", err - } - claim.AID = aid - claim.UID = refresh.UID - claim.Name = refresh.Name - claim.Icon = refresh.Icon - claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(cfg.Config.TokenExpire)) - if typ == "app" { - if refresh.AID == aid { - // refresh token - acList := make(auth.Access, 0, 10) - logv.AssertError(cfg.DB().Table("accesses a"). - Select("a.name, a.t_id, a.level"). - Joins("INNER JOIN user_roles ur ON ur.role_id = a.role_id AND ur.user_id = ? AND a.app_id = ?", refresh.UID, aid). - Scan(&acList).Error) - claim.Access = acList - if aid == cfg.Config.ID { - return auth.GenJwt(claim) - } - app := &models.App{} - err = cfg.DB().Where("id = ?", aid).First(app).Error - if err != nil { - return "", err - } - return auth.GenJwtWithKey(claim, app.Key) - } else if refresh.ID == cfg.Config.ID { - // oa应用生成其他应用的refresh token - newToken := &models.Token{} - newToken.UserID = refresh.UID - newToken.AppID = aid - newToken.ExpiredAt = time.Now().Add(time.Hour * 24) - if opts.OverPerm != nil { - newToken.OverPerm = *opts.OverPerm - } - if opts.Device != nil { - newToken.Device = *opts.Device - } - newToken.Ip = x.GetRemoteIP() - logv.AssertError(cfg.DB().Create(newToken).Error) - // gen other app token - claim.ID = newToken.ID - claim.ExpiresAt = jwt.NewNumericDate(newToken.ExpiredAt) - return auth.GenJwt(claim) - } else { - return "", vigo.ErrNotPermitted - } - } else if typ == "ufs" { - claim.AID = refresh.AID - claim.UID = refresh.UID - claim.Name = refresh.Name - claim.Icon = refresh.Icon - claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(cfg.Config.TokenExpire)) - claim.Access = auth.Access{ - {Name: "fs", TID: "/", Level: auth.Do}, - } - token := logv.AssertFuncErr(auth.GenJwt(claim)) - - cookie := &http.Cookie{ - Name: "fstoken", // Cookie 的名称 - Value: token, // Cookie 的值 - Path: "/fs/u/", // Cookie 的路径,通常是根路径 - MaxAge: 600, // Cookie 的最大年龄,单位是秒 - HttpOnly: true, // 是否仅限 HTTP(S) 访问 - Secure: false, // 是否通过安全连接传输 Cookie - } - http.SetCookie(x.ResponseWriter(), cookie) - return token, nil - } else { - return "", vigo.ErrArgInvalid - } - } else { - return "", vigo.ErrArgInvalid - } -} diff --git a/api/token/get.go b/api/token/get.go deleted file mode 100644 index c0cd6c9..0000000 --- a/api/token/get.go +++ /dev/null @@ -1,48 +0,0 @@ -// -// get.go -// Copyright (C) 2025 veypi -// 2025-05-09 17:24 -// Distributed under terms of the MIT license. -// - -package token - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type getOpts struct { - ID string `json:"id" src:"path@token_id" desc:"令牌ID"` -} - -var _ = Router.Get("/{token_id}", "获取 Token 详情", tokenGet) - -func tokenGet(x *vigo.X, opts *getOpts) (*models.Token, error) { - data := &models.Token{} - err := cfg.DB().Where("id = ?", opts.ID).First(data).Error - return data, err -} - -type listOpts struct { - Limit int `json:"limit" src:"query" desc:"限制数量"` - UserID string `json:"user_id" gorm:"index;type:varchar(36)" src:"query" desc:"用户ID"` - AppID string `json:"app_id" gorm:"index;type:varchar(36)" src:"query" desc:"应用ID"` -} - -var _ = Router.Get("/", "获取 Token 列表", tokenList) - -func tokenList(x *vigo.X, opts *listOpts) ([]*models.Token, error) { - data := make([]*models.Token, 0, 10) - - query := cfg.DB() - if opts.UserID != "" { - query = query.Where("user_id = ?", opts.UserID) - } - if opts.AppID != "" { - query = query.Where("app_id = ?", opts.AppID) - } - err := query.Limit(opts.Limit).Find(&data).Error - return data, err -} diff --git a/api/token/init.go b/api/token/init.go deleted file mode 100644 index 251d557..0000000 --- a/api/token/init.go +++ /dev/null @@ -1,14 +0,0 @@ -// -// Copyright (C) 2024 veypi -// 2024-09-24 22:37:12 -// Distributed under terms of the MIT license. -// -// - -package token - -import ( - "github.com/veypi/vigo" -) - -var Router = vigo.NewRouter() diff --git a/api/user/create.go b/api/user/create.go deleted file mode 100644 index d9b7089..0000000 --- a/api/user/create.go +++ /dev/null @@ -1,114 +0,0 @@ -// -// create.go -// Copyright (C) 2025 veypi -// 2025-05-06 15:05 -// Distributed under terms of the MIT license. -// - -package user - -import ( - "encoding/base64" - "fmt" - "math/rand" - "strings" - "time" - - "github.com/google/uuid" - "github.com/veypi/vbase/api/sms" - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/libs/utils" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" - "gorm.io/gorm" -) - -var _ = Router.Post("/", "用户注册", vigo.SkipBefore, publicLimits, userPost) - -type postOpts struct { - Username string `json:"username" gorm:"varchar(100);unique;default:not null" src:"json" desc:"用户名"` - Code string `json:"code" gorm:"varchar(128)" src:"json" desc:"授权码/密码"` - Phone string `json:"phone" gorm:"varchar(50);unique;default:null" src:"json" desc:"手机号"` - VerifyCode string `json:"verify_code" src:"json" desc:"验证码"` - Region string `json:"region" src:"json" desc:"区域"` - - Nickname *string `json:"nickname" src:"json" desc:"昵称"` - Icon *string `json:"icon" src:"json" desc:"头像"` - Email *string `json:"email" gorm:"varchar(20);unique;default:null" src:"json" desc:"邮箱"` -} - -func userPost(x *vigo.X, opts *postOpts) (*models.User, error) { - data := &models.User{} - - if cfg.Config.SMS.Enable { - data.Phone = opts.Region + opts.Phone - data.Region = opts.Region - err := sms.VerifyCode(opts.Phone, opts.VerifyCode, opts.Region, "signup") - if err != nil { - return nil, vigo.ErrArgInvalid.WithArgs("verify code").WithString(err.Error()) - } - } - data.Username = opts.Username - data.Code = opts.Code - data.Salt = utils.RandSeq(16) - if len(data.Username) < 2 { - return nil, vigo.ErrArgInvalid.WithArgs("username length") - } - code, err := base64.URLEncoding.DecodeString(opts.Code) - if err != nil || len(code) < 8 { - return nil, vigo.ErrArgInvalid.WithArgs("code") - } - code = utils.PKCS7Padding(code, 32) - // We need ID for encryption, but it's not generated yet. - // We can generate it manually here since vigo.Model doesn't auto-generate it before Create - data.ID = strings.ReplaceAll(uuid.New().String(), "-", "") - - data.Code, err = utils.AesEncrypt([]byte(data.ID), code, []byte(data.Salt)) - if err != nil { - return nil, vigo.ErrArgInvalid.WithArgs("code") - } - ncode, err := utils.AesDecrypt([]byte(data.Code), code, []byte(data.Salt)) - if err != nil || string(ncode) != data.ID { - return nil, vigo.ErrInternalServer.WithString("code decrypt failed") - } - if opts.Nickname != nil { - data.Nickname = *opts.Nickname - } - if opts.Icon != nil { - data.Icon = *opts.Icon - } else { - data.Icon = fmt.Sprintf("https://public.veypi.com/img/avatar/%04d.jpg", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(220)) - } - if opts.Email != nil { - data.Email = *opts.Email - } - data.Status = 1 - err = cfg.DB().Transaction(func(tx *gorm.DB) error { - err := tx.Create(data).Error - if err != nil { - return err - } - app := &models.App{} - err = tx.Where("id = ?", cfg.Config.ID).First(app).Error - if err != nil { - return err - } - status := "ok" - switch app.Typ { - case "private": - return vigo.ErrNotPermitted.WithArgs("not enable register") - case "apply": - status = "applying" - case "public": - } - if app.Typ != "public" { - } - - return tx.Create(&models.AppUser{ - UserID: data.ID, - AppID: cfg.Config.ID, - Status: status, - }).Error - }) - return data, err -} diff --git a/api/user/init.go b/api/user/init.go deleted file mode 100644 index 290f0c0..0000000 --- a/api/user/init.go +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright (C) 2024 veypi -// 2024-09-23 16:28:13 -// Distributed under terms of the MIT license. -// -// Auto generated by OneBD. DO NOT EDIT - -package user - -import ( - "github.com/veypi/vbase/api/user/role" - "github.com/veypi/vbase/libs/auth" - "github.com/veypi/vigo" -) - -var Router = vigo.NewRouter() - -func init() { - Router.Extend("/{user_id}/user_role", role.Router).Use(auth.Check("user", "", 4)) -} diff --git a/api/user/login.go b/api/user/login.go deleted file mode 100644 index fb105e5..0000000 --- a/api/user/login.go +++ /dev/null @@ -1,114 +0,0 @@ -// -// login.go -// Copyright (C) 2025 veypi -// 2025-05-12 17:35 -// Distributed under terms of the MIT license. -// - -package user - -import ( - "encoding/base64" - "time" - - "github.com/golang-jwt/jwt/v5" - "github.com/veypi/vbase/api/sms" - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/libs/auth" - "github.com/veypi/vbase/libs/utils" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" - "github.com/veypi/vigo/contrib/limiter" - "github.com/veypi/vigo/logv" -) - -var publicLimits = limiter.NewAdvancedRequestLimiter(time.Minute*5, 20, time.Second*3, nil).Limit - -var _ = Router.Post("/login", "用户登录", - vigo.SkipBefore, publicLimits, - userLogin) - -type loginOpts struct { - UserName *string `json:"username" src:"json" desc:"用户名"` - Code *string `json:"code" src:"json" desc:"密码/授权码"` - - VerifyCode *string `json:"verify_code" src:"json" desc:"验证码"` - Region *string `json:"region" src:"json" desc:"区域"` - Phone *string `json:"phone" src:"json" desc:"手机号"` - - Email *string `json:"email" src:"json" desc:"邮箱"` - Type *string `json:"type" src:"json" desc:"登录类型"` - AppID *string `json:"app_id" src:"json" desc:"应用ID"` - Device *string `json:"device" src:"json" desc:"设备信息"` -} - -func userLogin(x *vigo.X, opts *loginOpts) (string, error) { - // Implement login logic here - // For example, validate user credentials and return a token - user := &models.User{} - query := cfg.DB() - typ := "" - if opts.Type != nil { - typ = *opts.Type - } - switch typ { - case "phone": - query = query.Where("phone = ?", *opts.Region+*opts.Phone) - case "email": - query = query.Where("email = ?", *opts.Email) - default: - query = query.Where("username = ?", *opts.UserName) - } - err := query.First(user).Error - if err != nil { - return "", vigo.ErrNotFound - } - logv.Info().Str("user", user.ID).Msg("login") - if opts.VerifyCode != nil { - err = sms.VerifyCode(*opts.Phone, *opts.VerifyCode, *opts.Region, "signin") - if err != nil { - logv.Warn().Msgf("verify code: %v", err) - return "", vigo.ErrNotAuthorized.WithError(err) - } - } else { - code, err := base64.URLEncoding.DecodeString(*opts.Code) - if err != nil { - return "", vigo.ErrArgInvalid.WithArgs("code") - } - - ncode, err := utils.AesDecrypt([]byte(user.Code), utils.PKCS7Padding(code, 32), []byte(user.Salt)) - if err != nil || string(ncode) != user.ID { - return "", vigo.ErrNotAuthorized - } - } - aid := cfg.Config.ID - if opts.AppID != nil && *opts.AppID != "" { - aid = *opts.AppID - } - - data := &models.Token{} - - data.UserID = user.ID - data.AppID = aid - data.ExpiredAt = time.Now().Add(time.Hour * 72) - if opts.Device != nil { - data.Device = *opts.Device - } - - err = cfg.DB().Create(data).Error - if err != nil { - return "", err - } - - claim := &auth.Claims{} - claim.ID = data.ID - claim.UID = user.ID - claim.AID = aid - claim.Name = user.Username - claim.Icon = user.Icon - claim.ExpiresAt = jwt.NewNumericDate(data.ExpiredAt) - claim.IssuedAt = jwt.NewNumericDate(time.Now()) - claim.Issuer = cfg.Config.ID - - return auth.GenJwt(claim) -} diff --git a/api/user/role/create.go b/api/user/role/create.go deleted file mode 100644 index 96fa09e..0000000 --- a/api/user/role/create.go +++ /dev/null @@ -1,35 +0,0 @@ -// -// crud.go -// Copyright (C) 2025 veypi -// 2025-05-06 15:12 -// Distributed under terms of the MIT license. -// - -package role - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -var _ = Router.Post("/", "创建用户角色", userRolePost) - -type postOpts struct { - UserID string `json:"user_id" src:"path" desc:"用户ID"` - RoleID string `json:"role_id" src:"json" desc:"角色ID"` - AppID string `json:"app_id" src:"json" desc:"应用ID"` - Status string `json:"status" src:"json" desc:"状态"` -} - -func userRolePost(x *vigo.X, opts *postOpts) (*models.UserRole, error) { - data := &models.UserRole{} - - data.UserID = opts.UserID - data.RoleID = opts.RoleID - data.AppID = opts.AppID - data.Status = opts.Status - err := cfg.DB().Create(data).Error - - return data, err -} diff --git a/api/user/role/del.go b/api/user/role/del.go deleted file mode 100644 index 08bb8bc..0000000 --- a/api/user/role/del.go +++ /dev/null @@ -1,26 +0,0 @@ -// -// del.go -// Copyright (C) 2025 veypi -// 2025-05-06 15:24 -// Distributed under terms of the MIT license. -// - -package role - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type deleteIDReq struct { - ID string `src:"path@id" desc:"记录ID"` -} - -var _ = Router.Delete("/{id}", "删除用户角色", userRoleDelete) - -func userRoleDelete(x *vigo.X, req *deleteIDReq) (*models.UserRole, error) { - data := &models.UserRole{} - err := cfg.DB().Where("id = ?", req.ID).Delete(data).Error - return data, err -} diff --git a/api/user/role/get.go b/api/user/role/get.go deleted file mode 100644 index 4eaf2b5..0000000 --- a/api/user/role/get.go +++ /dev/null @@ -1,68 +0,0 @@ -// -// get.go -// Copyright (C) 2025 veypi -// 2025-05-06 15:21 -// Distributed under terms of the MIT license. -// - -package role - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - - "github.com/veypi/vigo" -) - -type getIDReq struct { - ID string `src:"path@id" desc:"记录ID"` -} - -var _ = Router.Get("/{id}", "获取用户角色详情", userRoleGet) - -func userRoleGet(x *vigo.X, req *getIDReq) (*models.UserRole, error) { - data := &models.UserRole{} - err := cfg.DB().Where("id = ?", req.ID).First(data).Error - return data, err -} - -type listOpts struct { - UserID *string `json:"user_id" src:"path@user_id" desc:"用户ID"` - RoleID *string `json:"role_id" src:"query" desc:"角色ID"` - AppID *string `json:"app_id" src:"query" desc:"应用ID"` - Status *string `json:"status" src:"query" desc:"状态"` -} - -var _ = Router.Get("/", "获取用户角色列表", userRoleList) - -type userRoleListItem struct { - models.UserRole - Username string `json:"username"` - Nickname string `json:"nickname"` - Icon string `json:"icon"` - RoleName string `json:"role_name"` -} - -func userRoleList(x *vigo.X, opts *listOpts) ([]*userRoleListItem, error) { - data := make([]*userRoleListItem, 0, 10) - // data := make([]*M.UserRole, 0, 10) - - query := cfg.DB().Debug().Table("user_roles").Select("user_roles.*,users.username,users.nickname,users.icon,roles.name as role_name"). - Joins("JOIN users ON users.id = user_roles.user_id"). - Joins("JOIN roles ON roles.id = user_roles.role_id") - if opts.UserID != nil && *opts.UserID != "-" { - query = query.Where("user_id LIKE ?", opts.UserID) - } - if opts.RoleID != nil { - query = query.Where("role_id LIKE ?", opts.RoleID) - } - if opts.AppID != nil { - query = query.Where("app_id LIKE ?", opts.AppID) - } - if opts.Status != nil { - query = query.Where("status LIKE ?", opts.Status) - } - err := query.Scan(&data).Error - - return data, err -} diff --git a/api/user/role/init.go b/api/user/role/init.go deleted file mode 100644 index efc1a5b..0000000 --- a/api/user/role/init.go +++ /dev/null @@ -1,12 +0,0 @@ -// -// init.go -// Copyright (C) 2025 veypi -// 2025-05-06 15:12 -// Distributed under terms of the MIT license. -// - -package role - -import "github.com/veypi/vigo" - -var Router = vigo.NewRouter() diff --git a/api/user/role/patch.go b/api/user/role/patch.go deleted file mode 100644 index 46bfb8b..0000000 --- a/api/user/role/patch.go +++ /dev/null @@ -1,36 +0,0 @@ -// -// crud.go -// Copyright (C) 2025 veypi -// 2025-05-06 15:12 -// Distributed under terms of the MIT license. -// - -package role - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type patchOpts struct { - ID string `json:"id" src:"path@id" desc:"记录ID"` - Status *string `json:"status" src:"json" desc:"状态"` -} - -var _ = Router.Patch("/{id}", "更新用户角色", userRolePatch) - -func userRolePatch(x *vigo.X, opts *patchOpts) (*models.UserRole, error) { - data := &models.UserRole{} - err := cfg.DB().Where("id = ?", opts.ID).First(data).Error - if err != nil { - return nil, err - } - optsMap := make(map[string]any) - if opts.Status != nil { - optsMap["status"] = opts.Status - } - err = cfg.DB().Model(data).Updates(optsMap).Error - - return data, err -} diff --git a/api/user/user.go b/api/user/user.go deleted file mode 100644 index 8c5967d..0000000 --- a/api/user/user.go +++ /dev/null @@ -1,129 +0,0 @@ -package user - -import ( - "github.com/veypi/vbase/cfg" - "github.com/veypi/vbase/libs/auth" - "github.com/veypi/vbase/models" - "github.com/veypi/vigo" -) - -type userIDReq struct { - UserID string `src:"path@user_id" desc:"用户ID"` -} - -var _ = Router.Delete("/{user_id}", "删除用户", auth.Check("user", "user_id", auth.DoDelete, checkOwner), userDelete) - -func userDelete(x *vigo.X, req *userIDReq) (*models.User, error) { - data := &models.User{} - err := cfg.DB().Where("id = ?", req.UserID).Delete(data).Error - return data, err -} - -var _ = Router.Get("/{user_id}", "获取用户详情", auth.Check("user", "user_id", auth.DoRead, checkOwner), userGet) - -func checkOwner(x *vigo.X, data any) bool { - u, ok1 := data.(*models.User) - return ok1 && u.ID == x.PathParams.Get("user_id") -} - -func userGet(x *vigo.X, req *userIDReq) (*models.User, error) { - data := &models.User{} - err := cfg.DB().Where("id = ?", req.UserID).First(data).Error - return data, err -} - -type listReq struct { - Page int `src:"query" json:"page" desc:"页码"` - PageSize int `src:"query" json:"page_size" desc:"每页数量"` - Keyword string `src:"query" json:"keyword" desc:"搜索关键词"` -} - -type listResp struct { - Items []*models.User `json:"items"` - Total int64 `json:"total"` - Page int `json:"page"` - PageSize int `json:"page_size"` - TotalPages int `json:"total_pages"` -} - -func userList(x *vigo.X, req *listReq) (*listResp, error) { - if req.Page <= 0 { - req.Page = 1 - } - if req.PageSize <= 0 || req.PageSize > 100 { - req.PageSize = 10 - } - - var total int64 - query := cfg.DB().Model(&models.User{}) - - if req.Keyword != "" { - query = query.Where("username LIKE ? OR nickname LIKE ? OR email LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%", "%"+req.Keyword+"%") - } - - if err := query.Count(&total).Error; err != nil { - return nil, err - } - - var items []*models.User - offset := (req.Page - 1) * req.PageSize - if err := query.Offset(offset).Limit(req.PageSize).Find(&items).Error; err != nil { - return nil, err - } - - totalPages := int((total + int64(req.PageSize) - 1) / int64(req.PageSize)) - - return &listResp{ - Items: items, - Total: total, - Page: req.Page, - PageSize: req.PageSize, - TotalPages: totalPages, - }, nil -} - -var _ = Router.Get("/", "用户列表", auth.Check("user", "", auth.DoUpdate), userList) - -var _ = Router.Patch("/{user_id}", "更新用户", auth.Check("user", "user_id", auth.DoUpdate, checkOwner), userPatch) - -type patchOpts struct { - ID string `json:"id" src:"path@user_id" desc:"用户ID"` - Nickname *string `json:"nickname" src:"json" desc:"昵称"` - Icon *string `json:"icon" src:"json" desc:"头像"` - - Username *string `json:"username" src:"json" desc:"用户名"` - Email *string `json:"email" src:"json" desc:"邮箱"` - Phone *string `json:"phone" src:"json" desc:"手机号"` - Status *uint `json:"status" src:"json" desc:"状态"` -} - -func userPatch(x *vigo.X, opts *patchOpts) (*models.User, error) { - data := &models.User{} - err := cfg.DB().Where("id = ?", opts.ID).First(data).Error - if err != nil { - return nil, vigo.ErrNotFound - } - - optsMap := make(map[string]any) - if opts.Username != nil { - optsMap["username"] = opts.Username - } - if opts.Nickname != nil { - optsMap["nickname"] = opts.Nickname - } - if opts.Icon != nil { - optsMap["icon"] = opts.Icon - } - if opts.Email != nil { - optsMap["email"] = opts.Email - } - if opts.Phone != nil { - optsMap["phone"] = opts.Phone - } - if opts.Status != nil { - optsMap["status"] = opts.Status - } - err = cfg.DB().Model(data).Updates(optsMap).Error - - return data, err -} diff --git a/cfg/cfg.go b/cfg/cfg.go index b8fab76..e51bea3 100644 --- a/cfg/cfg.go +++ b/cfg/cfg.go @@ -8,41 +8,18 @@ package cfg import ( - "os" - "time" + "github.com/veypi/vigo/contrib/config" ) type Options struct { - DSN string `json:"dsn"` // Data Source Name - DB string `json:"db"` // DB type: mysql, postgres, sqlite - RedisAddr string `json:"redis_addr"` - - ID string `json:"id"` - Key string `json:"key"` - TokenExpire time.Duration `json:"token_expire"` // Token expiration time in seconds - SMS *SMSConfig `json:"sms"` + DSN string `json:"dsn"` // Data Source Name + DB string `json:"db"` // DB type: mysql, postgres, sqlite + Redis config.Redis + Key config.Key `json:"key"` + SMS *SMSConfig `json:"sms"` } var Config = &Options{ - TokenExpire: time.Minute * 120, - ID: getEnv("APPID", "test"), - Key: getEnv("APPKEY", "asdfghjklqwertyuiopzxcvbnm1234567890"), - DSN: getEnv("DSN", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Local"), - DB: getEnv("DB", "mysql"), - SMS: defaultSMS(), -} - -func getEnv(key, defaultValue string) string { - v := os.Getenv(key) - if v != "" { - return v - } - return defaultValue -} - -type PublicSettings struct { - SMS string `json:"sms"` - SMSKey string `json:"sms_key"` - SMSSecret string `json:"sms_secret"` - SMSRegion string `json:"sms_region"` + DB: "sqlite", + SMS: defaultSMS(), } diff --git a/cfg/dev.yml b/cfg/dev.yml deleted file mode 100644 index 2bd2463..0000000 --- a/cfg/dev.yml +++ /dev/null @@ -1,8 +0,0 @@ -host: 0.0.0.0 -port: 4001 -loggerpath: "" -loggerlevel: debug -dsn: root:123456@tcp(127.0.0.1:3306)/oa?charset=utf8&parseTime=True&loc=Local -db: mysql -id: "ascc2fdf2" -key: "sasdsdsasd123sdf" diff --git a/errs/errors.go b/errs/errors.go deleted file mode 100644 index 1b44b57..0000000 --- a/errs/errors.go +++ /dev/null @@ -1,18 +0,0 @@ -// -// errors.go -// Copyright (C) 2024 veypi -// 2024-08-01 15:40 -// Distributed under terms of the MIT license. -// - -package errs - -import "github.com/veypi/vigo" - -var ( - AuthNotFound = vigo.NewError("auth not found").WithCode(40100) - AuthFailed = vigo.NewError("auth failed").WithCode(40101) - AuthExpired = vigo.NewError("auth expired").WithCode(40102) - AuthInvalid = vigo.NewError("auth invalid").WithCode(40103) - AuthNoPerm = vigo.NewError("auth no permission").WithCode(40104) -) diff --git a/libs/auth/access.go b/libs/auth/access.go deleted file mode 100644 index 2bea64f..0000000 --- a/libs/auth/access.go +++ /dev/null @@ -1,68 +0,0 @@ -// -// access.go -// Copyright (C) 2024 veypi -// 2024-09-23 19:37 -// Distributed under terms of the MIT license. -// - -package auth - -import ( - "strings" - - "github.com/golang-jwt/jwt/v5" -) - -type AuthLevel = int - -const ( - DoNone = 0 - Do = 1 - DoRead = 1 - DoCreate = 2 - DoUpdate = 3 - DoDelete = 4 - DoAll = 5 -) - -type Access []*struct { - Name string `json:"name"` - TID string `json:"tid"` - Level AuthLevel `json:"level"` -} - -func (a *Access) Check(target string, tid string, l AuthLevel) bool { - if l == DoNone { - return true - } - for _, line := range *a { - if line.Name == target && line.Level >= l { - if line.TID == "" || line.TID == tid { - return true - } - } - } - return false -} -func (a *Access) CheckPrefix(target string, tid string, l AuthLevel) bool { - if l == DoNone { - return true - } - for _, line := range *a { - if line.Name == target && line.Level >= l { - if line.TID == "" || strings.HasPrefix(tid, line.TID) { - return true - } - } - } - return false -} - -type Claims struct { - UID string `json:"uid"` - AID string `json:"aid"` - Name string `json:"name"` - Icon string `json:"icon"` - Access Access `json:"access"` - jwt.RegisteredClaims -} diff --git a/libs/auth/jwt.go b/libs/auth/jwt.go deleted file mode 100644 index c2db2e5..0000000 --- a/libs/auth/jwt.go +++ /dev/null @@ -1,117 +0,0 @@ -// -// jwt.go -// Copyright (C) 2024 veypi -// 2024-09-23 18:28 -// Distributed under terms of the MIT license. -// - -package auth - -import ( - "errors" - "fmt" - "strings" - "time" - - "github.com/golang-jwt/jwt/v5" - "github.com/veypi/vbase/cfg" - "github.com/veypi/vigo" -) - -var ( - AuthNotFound = vigo.NewError("auth not found").WithCode(40100) - AuthFailed = vigo.NewError("auth failed").WithCode(40101) - AuthExpired = vigo.NewError("auth expired").WithCode(40102) - AuthInvalid = vigo.NewError("auth invalid").WithCode(40103) - AuthNoPerm = vigo.NewError("auth no permission").WithCode(40104) -) - -func GenJwt(claim *Claims) (string, error) { - return GenJwtWithKey(claim, cfg.Config.Key) -} - -func GenJwtWithKey(claim *Claims, key string) (string, error) { - if claim.ExpiresAt == nil { - claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Hour)) - } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim) - return token.SignedString([]byte(key)) -} - -func ParseJwt(tokenString string, keys ...string) (*Claims, error) { - key := cfg.Config.Key - if len(keys) > 0 { - key = keys[0] - } - claims := &Claims{} - token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (any, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) - } - return []byte(key), nil - }) - if errors.Is(err, jwt.ErrTokenExpired) { - return nil, AuthExpired - } - - if err != nil || !token.Valid { - return nil, AuthInvalid - } - return claims, nil -} - -func CheckJWT(x *vigo.X) (*Claims, error) { - authHeader := x.Request.Header.Get("Authorization") - if authHeader == "" { - authHeader = x.Request.URL.Query().Get("Authorization") - if authHeader == "" { - return nil, AuthNotFound - } - } - // Token is typically in the format "Bearer " - tokenString := strings.TrimPrefix(authHeader, "Bearer ") - - // Parse the token - claims, err := ParseJwt(tokenString) - if err != nil { - return nil, err - } - x.Set("token", claims) - x.Set("uid", claims.UID) - - return claims, nil -} - -type CustomCheckFunc = func(x *vigo.X, data any) bool - -func Check(target string, pid string, l AuthLevel, funcs ...CustomCheckFunc) func(x *vigo.X, data any) (any, error) { - return func(x *vigo.X, data any) (any, error) { - var err error - claims, ok := x.Get("token").(*Claims) - if !ok { - claims, err = CheckJWT(x) - if err != nil { - return nil, err - } - } - tid := "" - if strings.HasPrefix(pid, "@") { - tid, _ = x.Get(pid[1:]).(string) - } - if strings.HasPrefix(pid, "{") && strings.HasSuffix(pid, "}") { - tid = x.PathParams.Get(pid[1 : len(pid)-1]) - } - if strings.HasPrefix(pid, ":") { - tid = x.PathParams.Get(pid[1:]) - } - if !claims.Access.Check(target, tid, l) { - err = AuthNoPerm - } - for _, fn := range funcs { - if fn(x, data) { - return data, nil - } - } - return data, err - } -} diff --git a/models/app.go b/models/app.go deleted file mode 100644 index 3bc5d76..0000000 --- a/models/app.go +++ /dev/null @@ -1,106 +0,0 @@ -package models - -import ( - "github.com/veypi/vigo" - "github.com/veypi/vigo/logv" - "gorm.io/gorm" -) - -const AUSTATUS_OK = "ok" -const AUSTATUS_NO = "no" -const AUSTATUS_APPLYING = "applying" -const AUSTATUS_REJECT = "reject" - -type App struct { - vigo.Model - Name string `json:"name" src:"json" desc:"应用名称"` - Icon string `json:"icon" src:"json" desc:"图标"` - Des string `json:"des" src:"json" desc:"描述"` - Typ string `json:"typ" gorm:"default:public" src:"json" desc:"类型"` - Status string `json:"status" gorm:"default:ok" src:"json" desc:"状态"` - InitRoleID *string `json:"init_role_id" gorm:"index;type:varchar(36);default: null" src:"json" desc:"初始角色ID"` - InitRole *Role `json:"init_role" gorm:"foreignKey:InitRoleID;references:ID"` - InitUrl string `json:"init_url" src:"json" desc:"初始URL"` - UserCount uint `json:"user_count" default:"0"` - Key string `json:"-"` -} - -type AppUser struct { - vigo.Model - AppID string `json:"app_id" src:"path" desc:"应用ID"` - App *App `json:"app" gorm:"foreignKey:AppID;references:ID"` - - UserID string `json:"user_id" src:"json" desc:"用户ID"` - User *User `json:"user" gorm:"foreignKey:UserID;references:ID"` - - Status string `json:"status" src:"json" desc:"状态"` -} - -func (m *AppUser) onOk(tx *gorm.DB) (err error) { - app := &App{} - logv.AssertError(tx.Where("id = ?", m.AppID).First(app).Error) - if app.InitRoleID != nil { - urList := make([]*UserRole, 0, 2) - logv.AssertError(tx.Debug().Where("app_id = ? AND user_id = ?", m.AppID, m.UserID).Find(&urList).Error) - if len(urList) == 0 { - return tx.Create(&UserRole{ - AppID: m.AppID, - UserID: m.UserID, - RoleID: *app.InitRoleID, - Status: "ok", - }).Error - } - } - return nil -} - -func (m *AppUser) AfterCreate(tx *gorm.DB) error { - if m.Status == AUSTATUS_OK { - logv.AssertError(m.onOk(tx)) - } - return tx.Model(&App{}).Where("id = ?", m.AppID).Update("user_count", gorm.Expr("user_count + ?", 1)).Error -} - -func (m *AppUser) AfterUpdate(tx *gorm.DB) error { - if m.Status == AUSTATUS_OK { - return m.onOk(tx) - } - return nil -} - -type Resource struct { - vigo.Model - AppID string `json:"app_id" src:"path@app_id" desc:"应用ID"` - App *App `json:"-" gorm:"foreignKey:AppID;references:ID"` - Name string `json:"name" src:"json" desc:"名称"` - Des string `json:"des" src:"json" desc:"描述"` -} - -type Role struct { - vigo.Model - AppID string `json:"app_id" src:"path@app_id" desc:"应用ID"` - App *App `json:"-" gorm:"foreignKey:AppID;references:ID"` - Name string `json:"name" src:"json" desc:"名称"` - Des string `json:"des" src:"json" desc:"描述"` - UserCount uint `json:"user_count" default:"0"` - Access []*Access `json:"-"` -} - -type Access struct { - vigo.Model - AppID string `json:"app_id" gorm:"index;type:varchar(36)" src:"path@app_id" desc:"应用ID"` - App *App `json:"-" gorm:"foreignKey:AppID;references:ID"` - - UserID *string `json:"user_id" gorm:"index;type:varchar(36);default: null" src:"json" desc:"用户ID"` - User *User `json:"-" gorm:"foreignKey:UserID;references:ID"` - - RoleID *string `json:"role_id" gorm:"index;type:varchar(36);default: null" src:"json" desc:"角色ID"` - Role *Role `json:"-" gorm:"foreignKey:RoleID;references:ID"` - - ResourceID *string `json:"resource_id" gorm:"index;type:varchar(36);default: null" src:"json" desc:"资源ID"` - Resource *Resource `json:"-" gorm:"foreignKey:ResourceID;references:ID"` - - Name string `json:"name" src:"json" desc:"名称"` - TID string `json:"tid" src:"json" desc:"资源ID"` - Level uint `json:"level" src:"json" desc:"级别"` -} diff --git a/models/init.go b/models/init.go index c9e4225..3bb0c26 100644 --- a/models/init.go +++ b/models/init.go @@ -7,19 +7,14 @@ package models import ( - "strings" - "github.com/veypi/vbase/cfg" - "github.com/google/uuid" "github.com/veypi/vigo" - "github.com/veypi/vigo/logv" ) var AllModels = &vigo.ModelList{} func init() { - AllModels.Append(User{}, AppUser{}, Resource{}, Access{}, Role{}, UserRole{}, Token{}, App{}, SMSCode{}, SMSLog{}) } func Migrate() error { @@ -31,46 +26,5 @@ func Drop() error { } func InitDB() error { - app := &App{} - app.ID = cfg.Config.ID - app.Name = "OA" - app.Icon = "/favicon.ico" - app.Key = cfg.Config.Key - logv.AssertError(cfg.DB().Where("id = ?", app.ID).Attrs(app).FirstOrCreate(app).Error) - initRole := map[string]map[string]uint{ - "user": {"admin": 5, "normal": 1}, - "app": {"admin": 5, "normal": 1}, - } - adminID := "" - for r, roles := range initRole { - resource := &Resource{ - AppID: app.ID, - Name: r, - } - logv.AssertError(cfg.DB().Where("app_id = ? AND name = ?", app.ID, r).FirstOrCreate(resource).Error) - for rName, l := range roles { - role := &Role{} - logv.AssertError(cfg.DB().Where("app_id = ? AND name = ?", app.ID, rName).Attrs(&Role{ - Model: vigo.Model{ - ID: strings.ReplaceAll(uuid.New().String(), "-", ""), - }, - AppID: app.ID, - Name: rName, - }).FirstOrCreate(role).Error) - logv.AssertError(cfg.DB().Where("app_id = ? AND role_id = ? AND name = ?", app.ID, role.ID, r).FirstOrCreate(&Access{ - AppID: app.ID, - RoleID: &role.ID, - ResourceID: &resource.ID, - Name: r, - Level: l, - }).Error) - if rName == "admin" { - adminID = role.ID - } - } - } - if app.InitRoleID == nil { - logv.AssertError(cfg.DB().Model(app).Update("init_role_id", adminID).Error) - } return nil } diff --git a/models/token.go b/models/token.go deleted file mode 100644 index 5f756f9..0000000 --- a/models/token.go +++ /dev/null @@ -1,22 +0,0 @@ -package models - -import ( - "time" - - "github.com/veypi/vigo" -) - -// refresh token,由oa 秘钥签发,有效期长, 存储在token表 -// app token, 由app 秘钥签发,有效期短, 不存储 -// OverPerm 非oa应用获取oa数据的权限,由用户设定 -type Token struct { - vigo.Model - UserID string `json:"user_id" gorm:"index;type:varchar(36)" src:"json" desc:"用户ID"` - User *User `json:"-"` - AppID string `json:"app_id" gorm:"index;type:varchar(36)" src:"json" desc:"应用ID"` - App *App `json:"-"` - ExpiredAt time.Time `json:"expired_at" src:"json" desc:"过期时间"` - OverPerm string `json:"over_perm" src:"json" desc:"覆盖权限"` - Device string `json:"device" src:"json" desc:"设备信息"` - Ip string `json:"ip"` -} diff --git a/models/user.go b/models/user.go deleted file mode 100644 index 22f7533..0000000 --- a/models/user.go +++ /dev/null @@ -1,43 +0,0 @@ -package models - -import ( - "github.com/veypi/vigo" - "gorm.io/gorm" -) - -// salt for user user password gen aes code -// salt 32 hex / 16 byte / 128 bit -// code 64 hex / 32 byte / 256 bit -type User struct { - vigo.Model - Username string `json:"username" gorm:"type:varchar(100);unique;default:not null" src:"json" desc:"用户名"` - Nickname string `json:"nickname" gorm:"type:varchar(100)" src:"json" desc:"昵称"` - Icon string `json:"icon" src:"json" desc:"头像"` - - Email string `json:"email" gorm:"unique;type:varchar(64);null;default:null" src:"json" desc:"邮箱"` - Phone string `json:"phone" gorm:"type:varchar(64);unique;null;default:null" src:"json" desc:"手机号"` - Region string `json:"region" gorm:"type:varchar(32);default:null" src:"json" desc:"地区"` - - Status uint `json:"status" src:"json" desc:"状态"` - - Salt string `json:"-" gorm:"type:varchar(32)"` - Code string `json:"-" gorm:"type:varchar(64)" src:"json" desc:"代码"` -} - -type UserRole struct { - vigo.Model - UserID string `json:"user_id" src:"path@user_id" desc:"用户ID"` - User *User `json:"-" gorm:"foreignKey:UserID;references:ID"` - - RoleID string `json:"role_id" src:"json" desc:"角色ID"` - Role *Role `json:"-" gorm:"foreignKey:RoleID;references:ID"` - - AppID string `json:"app_id" src:"json" desc:"应用ID"` - App *App `json:"-" gorm:"foreignKey:AppID;references:ID"` - - Status string `json:"status"` -} - -func (m *UserRole) AfterCreate(tx *gorm.DB) error { - return tx.Model(&Role{}).Where("id = ?", m.RoleID).Update("user_count", gorm.Expr("user_count + ?", 1)).Error -}