remove old

v3
veypi 4 months ago
parent 809bba7417
commit 45708ca4f0

@ -1,45 +0,0 @@
//
// create.go
// Copyright (C) 2024 veypi <i@veypi.com>
// 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
}

@ -1,38 +0,0 @@
//
// del.go
// Copyright (C) 2024 veypi <i@veypi.com>
// 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
}

@ -1,30 +0,0 @@
//
// get.go
// Copyright (C) 2024 veypi <i@veypi.com>
// 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
}

@ -1,14 +0,0 @@
//
// Copyright (C) 2024 veypi <i@veypi.com>
// 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()

@ -1,64 +0,0 @@
//
// list.go
// Copyright (C) 2024 veypi <i@veypi.com>
// 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
}

@ -1,56 +0,0 @@
//
// patch.go
// Copyright (C) 2024 veypi <i@veypi.com>
// 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
}

@ -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
}

@ -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
}

@ -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
}

@ -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
}

@ -1,14 +0,0 @@
//
// init.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 2025-05-13 16:47
// Distributed under terms of the MIT license.
//
package app_user
import (
"github.com/veypi/vigo"
)
var Router = vigo.NewRouter()

@ -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
}

@ -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
}

@ -1,27 +0,0 @@
//
// Copyright (C) 2024 veypi <i@veypi.com>
// 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)
}

@ -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
}

@ -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
}

@ -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
}

@ -1,14 +0,0 @@
//
// init.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 2025-05-13 16:45
// Distributed under terms of the MIT license.
//
package resource
import (
"github.com/veypi/vigo"
)
var Router = vigo.NewRouter()

@ -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
}

@ -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
}

@ -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
}

@ -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
}

@ -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
}

@ -1,14 +0,0 @@
//
// init.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 2025-05-13 16:49
// Distributed under terms of the MIT license.
//
package role
import (
"github.com/veypi/vigo"
)
var Router = vigo.NewRouter()

@ -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
}

@ -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
}

@ -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,
}
}

@ -1,14 +0,0 @@
//
// init.go
// Copyright (C) 2025 veypi <i@veypi.com>
//
// Distributed under terms of the MIT license.
//
package sms
import "github.com/veypi/vigo"
var Router = vigo.NewRouter()

@ -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
}

@ -1,90 +0,0 @@
//
// utils.go
// Copyright (C) 2025 veypi <i@veypi.com>
//
// 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
}

@ -1,73 +0,0 @@
//
// validate.go
// Copyright (C) 2025 veypi <i@veypi.com>
//
// 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
}

@ -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
}

@ -1,133 +0,0 @@
//
// post.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 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
}
}

@ -1,48 +0,0 @@
//
// get.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 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
}

@ -1,14 +0,0 @@
//
// Copyright (C) 2024 veypi <i@veypi.com>
// 2024-09-24 22:37:12
// Distributed under terms of the MIT license.
//
//
package token
import (
"github.com/veypi/vigo"
)
var Router = vigo.NewRouter()

@ -1,114 +0,0 @@
//
// create.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 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
}

@ -1,20 +0,0 @@
//
// Copyright (C) 2024 veypi <i@veypi.com>
// 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))
}

@ -1,114 +0,0 @@
//
// login.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 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)
}

@ -1,35 +0,0 @@
//
// crud.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 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
}

@ -1,26 +0,0 @@
//
// del.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 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
}

@ -1,68 +0,0 @@
//
// get.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 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
}

@ -1,12 +0,0 @@
//
// init.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 2025-05-06 15:12
// Distributed under terms of the MIT license.
//
package role
import "github.com/veypi/vigo"
var Router = vigo.NewRouter()

@ -1,36 +0,0 @@
//
// crud.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 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
}

@ -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
}

@ -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(),
}

@ -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"

@ -1,18 +0,0 @@
//
// errors.go
// Copyright (C) 2024 veypi <i@veypi.com>
// 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)
)

@ -1,68 +0,0 @@
//
// access.go
// Copyright (C) 2024 veypi <i@veypi.com>
// 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
}

@ -1,117 +0,0 @@
//
// jwt.go
// Copyright (C) 2024 veypi <i@veypi.com>
// 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 <token>"
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
}
}

@ -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:"级别"`
}

@ -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
}

@ -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"`
}

@ -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
}
Loading…
Cancel
Save