package aeusadmin
import (
"context"
"html/template"
"os"
"path"
"reflect"
"strconv"
"strings"
"git.nobla.cn/golang/aeus-admin/defaults"
"git.nobla.cn/golang/aeus-admin/models"
adminTypes "git.nobla.cn/golang/aeus-admin/types"
"git.nobla.cn/golang/aeus/pkg/errors"
"git.nobla.cn/golang/aeus/pkg/pool"
"git.nobla.cn/golang/aeus/transport/http"
"git.nobla.cn/golang/rest"
"git.nobla.cn/golang/rest/inflector"
"git.nobla.cn/golang/rest/types"
restTypes "git.nobla.cn/golang/rest/types"
"gorm.io/gorm"
)
// getModels 获取预定义的模型列表
func getModels() []any {
return []any{
&models.Department{},
&models.Role{},
&models.User{},
&models.Menu{},
&models.Permission{},
&models.RolePermission{},
&models.Setting{},
}
}
// checkModelMenu 检查模型菜单
func checkModelMenu(db *gorm.DB, viewPath string, apiPrefix string, model *rest.Model, translate Translate) (value *models.Menu, err error) {
refVal := reflect.New(model.Value().Type()).Interface()
if v, ok := refVal.(adminTypes.MenuModel); ok {
row := v.GetMenu()
value = &models.Menu{}
if row.Name == "" {
row.Name = inflector.Camelize(model.Naming().ModuleName) + inflector.Camelize(model.Naming().Singular)
}
if err = db.Where("name = ?", row.Name).First(value).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
if row.Parent != "" {
parentModel := &models.Menu{}
if err = parentModel.FindOne(db, "name = ?", row.Parent); err == nil {
value.ParentId = parentModel.Id
}
}
value.Name = row.Name
value.Hidden = row.Hidden
value.Icon = row.Icon
value.Label = row.Label
value.Uri = row.Uri
value.ViewPath = row.ViewPath
if value.Label == "" {
value.Label = inflector.Camel2words(model.Naming().Pluralize)
if translate != nil {
value.Label = translate.Menu(model, value.Label)
}
}
if value.Uri == "" {
value.Uri = strings.TrimPrefix(model.Uri(types.ScenarioList), apiPrefix)
}
if value.ViewPath == "" {
value.ViewPath = path.Join(viewPath, model.ModuleName(), model.Naming().Singular, "Index.vue")
}
err = db.Create(value).Error
}
}
} else {
menuName := inflector.Camelize(model.Naming().ModuleName) + inflector.Camelize(model.Naming().Singular)
value = &models.Menu{}
if err = db.Where("name = ?", menuName).First(value).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
value.Name = menuName
value.ParentId = 0
value.Label = inflector.Camel2words(model.Naming().Pluralize)
if translate != nil {
value.Label = translate.Menu(model, value.Label)
}
value.Uri = strings.TrimPrefix(model.Uri(types.ScenarioList), apiPrefix)
value.ViewPath = path.Join(viewPath, model.ModuleName(), model.Naming().Singular, "Index.vue")
err = db.Create(value).Error
}
}
}
return
}
// checkModelPermission 检查模型权限是否写入到数据库
func checkModelPermission(db *gorm.DB, menuId int64, scene string, model *rest.Model, translate Translate) (permissionModel *models.Permission, err error) {
permissionModel = &models.Permission{}
permission := model.Permission(scene)
if err = db.Where("permission = ?", permission).First(permissionModel).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
permissionModel.MenuId = menuId
permissionModel.Label = scene
if translate != nil {
permissionModel.Label = translate.Permission(model, scene, permissionModel.Label)
}
permissionModel.Permission = permission
err = db.Create(permissionModel).Error
}
}
return
}
// checkModel 检查模型
func checkModel(opts *options, model *rest.Model) (err error) {
var (
menuModel *models.Menu
)
tx := opts.db.Begin()
if menuModel, err = checkModelMenu(tx, opts.viewPrefix, opts.apiPrefix, model, opts.translate); err != nil {
tx.Rollback()
return
}
for _, s := range defaultScenarios {
if model.HasScenario(s) {
if _, err = checkModelPermission(tx, menuModel.Id, s, model, opts.translate); err != nil {
tx.Rollback()
return
}
}
}
tx.Commit()
return
}
// generateVueFile 生成Vue文件
func generateVueFile(prefix string, apiPrefix string, mv *rest.Model) (err error) {
filename := path.Join(prefix, mv.Naming().ModuleName, mv.Naming().Singular, "Index.vue")
if _, err = os.Stat(filename); err == nil {
return
}
dirname := path.Dir(filename)
if _, err = os.Stat(dirname); err != nil {
if err = os.MkdirAll(dirname, os.ModePerm); err != nil {
return
}
}
var (
temp *template.Template
)
if temp, err = template.New("vue").Parse(vueTemplate); err != nil {
return
}
permissions := make(map[string]string)
for _, s := range defaultScenarios {
if mv.HasScenario(s) {
permissions[s] = mv.Permission(s)
}
}
data := &vueTemplateData{
ModuleName: mv.ModuleName(),
TableName: mv.TableName(),
Permissions: permissions,
ApiPrefix: strings.TrimPrefix(apiPrefix, "/"),
}
writer := pool.GetBuffer()
defer pool.PutBuffer(writer)
if err = temp.Execute(writer, data); err != nil {
return
}
return os.WriteFile(filename, writer.Bytes(), 0644)
}
// initREST 初始化REST模块
func initREST(ctx context.Context, o *options) (err error) {
tx := o.db
if tx == nil {
return errors.ErrUnavailable
}
opts := make([]rest.Option, 0)
opts = append(opts, o.restOpts...)
opts = append(opts, rest.WithDB(tx))
if o.apiPrefix != "" {
opts = append(opts, rest.WithUriPrefix(o.apiPrefix))
}
if err = rest.Init(opts...); err != nil {
return
}
if err = tx.AutoMigrate(getModels()...); err != nil {
return
}
return
}
// initRBAC 初始化权限控制, 用于生成角色权限相关的信息
func initModels(ctx context.Context, o *options) (err error) {
var mv *rest.Model
for _, v := range getModels() {
moduleName := o.moduleName
if mm, ok := v.(adminTypes.ModuleModel); ok {
moduleName = mm.ModuleName()
}
if mv, err = rest.AutoMigrate(ctx, v, rest.WithModuleName(moduleName)); err != nil {
return
} else {
if err = checkModel(o, mv); err != nil {
return
}
if o.vuePath != "" {
if err = generateVueFile(o.vuePath, o.apiPrefix, mv); err != nil {
return
}
}
}
}
return
}
// AutoMigrate 自动生成一个模型的schema和权限的定义
func AutoMigrate(ctx context.Context, db *gorm.DB, model any, cbs ...Option) (err error) {
var (
mv *rest.Model
)
opts := newOptions(cbs...)
if mm, ok := model.(adminTypes.ModuleModel); ok {
moduleName := mm.ModuleName()
opts.restOpts = append(opts.restOpts, rest.WithModuleName(moduleName))
}
if mv, err = rest.AutoMigrate(ctx, model, opts.restOpts...); err != nil {
return
}
err = checkModel(opts, mv)
return
}
func registerRESTRoute(domain string, db *gorm.DB, hs *http.Server) {
handleListSchemas := func(ctx *http.Context) (err error) {
var (
schemas []*restTypes.Schema
)
scenario := ctx.Request().URL.Query().Get("scenario")
if scenario == "" {
schemas, err = rest.GetSchemas(
ctx.Request().Context(),
db.WithContext(ctx.Request().Context()),
"",
ctx.Param("module"),
ctx.Param("table"),
)
} else {
schemas, err = rest.VisibleSchemas(
ctx.Request().Context(),
db.WithContext(ctx.Request().Context()),
"",
ctx.Param("module"),
ctx.Param("table"),
scenario,
)
}
if err != nil {
return ctx.Error(errors.NotFound, err.Error())
} else {
return ctx.Success(schemas)
}
}
handleUpdateSchemas := func(ctx *http.Context) (err error) {
schemas := make([]*restTypes.Schema, 0)
if err = ctx.Bind(&schemas); err != nil {
return ctx.Error(errors.Invalid, err.Error())
}
for i := range schemas {
schemas[i].Domain = domain
}
if err = db.WithContext(ctx.Request().Context()).Transaction(func(tx *gorm.DB) (errTx error) {
for _, row := range schemas {
if errTx = tx.Save(row).Error; errTx != nil {
return
}
}
return
}); err == nil {
return ctx.Success(map[string]interface{}{
"count": len(schemas),
"state": "success",
})
} else {
return ctx.Error(errors.Unavailable, err.Error())
}
}
handleDeleteSchema := func(ctx *http.Context) (err error) {
id, _ := strconv.Atoi(ctx.Param("id"))
model := &restTypes.Schema{Id: uint64(id)}
if err = db.WithContext(ctx.Request().Context()).Delete(model).Error; err == nil {
return ctx.Success(map[string]any{
"id": id,
})
} else {
return ctx.Error(errors.Unavailable, err.Error())
}
}
hs.GET("/rest/schema/:module/:table", handleListSchemas)
hs.PUT("/rest/schema/:module/:table", handleUpdateSchemas)
hs.DELETE("/rest/schema/:id", handleDeleteSchema)
}
// Init 初始化模块
func Init(ctx context.Context, cbs ...Option) (err error) {
opts := newOptions(cbs...)
if err = initREST(ctx, opts); err != nil {
return
}
if err = defaults.Menu(opts.db); err != nil {
return
}
if err = initModels(ctx, opts); err != nil {
return
}
if opts.httpServer != nil {
registerRESTRoute(opts.domain, opts.db, opts.httpServer)
}
if err = defaults.Data(opts.db); err != nil {
return
}
return
}