refactor: refresh webui

This commit is contained in:
Jacky 2024-04-30 09:53:45 +08:00
parent 59d59dd3ed
commit 4c7e037b76
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
138 changed files with 2232 additions and 1071 deletions

View file

@ -0,0 +1,96 @@
package certificate
import (
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/cosy"
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query"
"github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin"
"github.com/spf13/cast"
"net/http"
)
func GetAcmeUser(c *gin.Context) {
u := query.AcmeUser
id := cast.ToInt(c.Param("id"))
user, err := u.FirstByID(id)
if err != nil {
api.ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, user)
}
func CreateAcmeUser(c *gin.Context) {
cosy.Core[model.AcmeUser](c).SetValidRules(gin.H{
"name": "required",
"email": "required,email",
"ca_dir": "omitempty",
}).BeforeExecuteHook(func(ctx *cosy.Ctx[model.AcmeUser]) {
if ctx.Model.CADir == "" {
ctx.Model.CADir = settings.ServerSettings.CADir
}
err := ctx.Model.Register()
if err != nil {
ctx.AbortWithError(err)
return
}
}).Create()
}
func ModifyAcmeUser(c *gin.Context) {
cosy.Core[model.AcmeUser](c).SetValidRules(gin.H{
"name": "omitempty",
"email": "omitempty,email",
"ca_dir": "omitempty",
}).BeforeExecuteHook(func(ctx *cosy.Ctx[model.AcmeUser]) {
if ctx.Model.CADir == "" {
ctx.Model.CADir = settings.ServerSettings.CADir
}
if ctx.OriginModel.Email != ctx.Model.Email ||
ctx.OriginModel.CADir != ctx.Model.CADir {
err := ctx.Model.Register()
if err != nil {
ctx.AbortWithError(err)
return
}
}
}).Modify()
}
func GetAcmeUserList(c *gin.Context) {
cosy.Core[model.AcmeUser](c).
SetFussy("name", "email").
PagingList()
}
func DestroyAcmeUser(c *gin.Context) {
cosy.Core[model.AcmeUser](c).Destroy()
}
func RecoverAcmeUser(c *gin.Context) {
cosy.Core[model.AcmeUser](c).Recover()
}
func RegisterAcmeUser(c *gin.Context) {
id := cast.ToInt(c.Param("id"))
u := query.AcmeUser
user, err := u.FirstByID(id)
if err != nil {
api.ErrHandler(c, err)
return
}
err = user.Register()
if err != nil {
api.ErrHandler(c, err)
return
}
_, err = u.Where(u.ID.Eq(id)).Updates(user)
if err != nil {
api.ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, user)
}

View file

@ -2,8 +2,8 @@ package certificate
import (
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/api/cosy"
"github.com/0xJacky/Nginx-UI/internal/cert"
"github.com/0xJacky/Nginx-UI/internal/cosy"
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin"

View file

@ -2,8 +2,8 @@ package certificate
import (
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/api/cosy"
"github.com/0xJacky/Nginx-UI/internal/cert/dns"
"github.com/0xJacky/Nginx-UI/internal/cosy"
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin"

View file

@ -23,3 +23,13 @@ func InitCertificateRouter(r *gin.RouterGroup) {
func InitCertificateWebSocketRouter(r *gin.RouterGroup) {
r.GET("domain/:name/cert", IssueCert)
}
func InitAcmeUserRouter(r *gin.RouterGroup) {
r.GET("acme_users", GetAcmeUserList)
r.GET("acme_user/:id", GetAcmeUser)
r.POST("acme_user", CreateAcmeUser)
r.POST("acme_user/:id", ModifyAcmeUser)
r.POST("acme_user/:id/register", RegisterAcmeUser)
r.DELETE("acme_user/:id", DestroyAcmeUser)
r.PATCH("acme_user/:id", RecoverAcmeUser)
}

View file

@ -1,117 +0,0 @@
package cosy
import (
"github.com/0xJacky/Nginx-UI/internal/logger"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"gorm.io/gorm"
)
var validate *validator.Validate
func init() {
validate = validator.New()
}
type Ctx[T any] struct {
ctx *gin.Context
rules gin.H
Payload map[string]interface{}
Model T
OriginModel T
table string
tableArgs []interface{}
abort bool
nextHandler *gin.HandlerFunc
skipAssociationsOnCreate bool
beforeDecodeHookFunc []func(ctx *Ctx[T])
beforeExecuteHookFunc []func(ctx *Ctx[T])
executedHookFunc []func(ctx *Ctx[T])
gormScopes []func(tx *gorm.DB) *gorm.DB
preloads []string
scan func(tx *gorm.DB) any
transformer func(*T) any
permanentlyDelete bool
SelectedFields []string
itemKey string
}
func Core[T any](c *gin.Context) *Ctx[T] {
return &Ctx[T]{
ctx: c,
gormScopes: make([]func(tx *gorm.DB) *gorm.DB, 0),
beforeExecuteHookFunc: make([]func(ctx *Ctx[T]), 0),
beforeDecodeHookFunc: make([]func(ctx *Ctx[T]), 0),
itemKey: "`id`",
skipAssociationsOnCreate: true,
}
}
func (c *Ctx[T]) SetTable(table string, args ...interface{}) *Ctx[T] {
c.table = table
c.tableArgs = args
return c
}
func (c *Ctx[T]) SetItemKey(key string) *Ctx[T] {
c.itemKey = key
return c
}
func (c *Ctx[T]) SetValidRules(rules gin.H) *Ctx[T] {
c.rules = rules
return c
}
func (c *Ctx[T]) SetPreloads(args ...string) *Ctx[T] {
c.preloads = append(c.preloads, args...)
return c
}
func (c *Ctx[T]) validate() (errs gin.H) {
c.Payload = make(gin.H)
_ = c.ctx.ShouldBindJSON(&c.Payload)
errs = validate.ValidateMap(c.Payload, c.rules)
if len(errs) > 0 {
logger.Debug(errs)
for k := range errs {
errs[k] = c.rules[k]
}
return
}
// Make sure that the key in c.Payload is also the key of rules
validated := make(map[string]interface{})
for k, v := range c.Payload {
if _, ok := c.rules[k]; ok {
validated[k] = v
}
}
c.Payload = validated
return
}
func (c *Ctx[T]) SetScan(scan func(tx *gorm.DB) any) *Ctx[T] {
c.scan = scan
return c
}
func (c *Ctx[T]) SetTransformer(t func(m *T) any) *Ctx[T] {
c.transformer = t
return c
}
func (c *Ctx[T]) AbortWithError(err error) {
c.abort = true
errHandler(c.ctx, err)
}
func (c *Ctx[T]) Abort() {
c.abort = true
}

View file

@ -1,81 +0,0 @@
package cosy
import (
"github.com/0xJacky/Nginx-UI/api/cosy/map2struct"
"github.com/0xJacky/Nginx-UI/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm/clause"
"net/http"
)
func (c *Ctx[T]) Create() {
errs := c.validate()
if len(errs) > 0 {
c.ctx.JSON(http.StatusNotAcceptable, gin.H{
"message": "Requested with wrong parameters",
"errors": errs,
})
return
}
db := model.UseDB()
c.beforeDecodeHook()
if c.abort {
return
}
err := map2struct.WeakDecode(c.Payload, &c.Model)
if err != nil {
errHandler(c.ctx, err)
return
}
c.beforeExecuteHook()
if c.abort {
return
}
if c.skipAssociationsOnCreate {
err = db.Omit(clause.Associations).Create(&c.Model).Error
} else {
err = db.Create(&c.Model).Error
}
if err != nil {
errHandler(c.ctx, err)
return
}
if len(c.executedHookFunc) > 0 {
for _, v := range c.executedHookFunc {
v(c)
if c.abort {
return
}
}
}
tx := db.Preload(clause.Associations)
for _, v := range c.preloads {
tx = tx.Preload(v)
}
tx.Table(c.table, c.tableArgs...).First(&c.Model)
if c.nextHandler != nil {
(*c.nextHandler)(c.ctx)
} else {
c.ctx.JSON(http.StatusOK, c.Model)
}
}
func (c *Ctx[T]) WithAssociations() *Ctx[T] {
c.skipAssociationsOnCreate = false
return c
}

View file

@ -1,39 +0,0 @@
package cosy
import (
"github.com/0xJacky/Nginx-UI/api/cosy/map2struct"
"github.com/gin-gonic/gin"
"net/http"
)
func (c *Ctx[T]) Custom(fx func(ctx *Ctx[T])) {
if c.abort {
return
}
errs := c.validate()
if len(errs) > 0 {
c.ctx.JSON(http.StatusNotAcceptable, gin.H{
"message": "Requested with wrong parameters",
"errors": errs,
})
return
}
c.beforeDecodeHook()
for k := range c.Payload {
c.SelectedFields = append(c.SelectedFields, k)
}
err := map2struct.WeakDecode(c.Payload, &c.Model)
if err != nil {
errHandler(c.ctx, err)
return
}
c.beforeExecuteHook()
fx(c)
}

View file

@ -1,113 +0,0 @@
package cosy
import (
"github.com/0xJacky/Nginx-UI/model"
"github.com/spf13/cast"
"gorm.io/gorm"
"net/http"
)
func (c *Ctx[T]) PermanentlyDelete() {
c.permanentlyDelete = true
c.Destroy()
}
func (c *Ctx[T]) Destroy() {
if c.abort {
return
}
id := c.ctx.Param("id")
c.beforeExecuteHook()
db := model.UseDB()
result := db
if cast.ToBool(c.ctx.Query("permanent")) || c.permanentlyDelete {
result = result.Unscoped()
}
if len(c.gormScopes) > 0 {
result = result.Scopes(c.gormScopes...)
}
var err error
session := result.Session(&gorm.Session{})
if c.table != "" {
err = session.Table(c.table, c.tableArgs...).Take(c.OriginModel, id).Error
} else {
err = session.First(&c.OriginModel, id).Error
}
if err != nil {
errHandler(c.ctx, err)
return
}
err = result.Delete(&c.OriginModel).Error
if err != nil {
errHandler(c.ctx, err)
return
}
if len(c.executedHookFunc) > 0 {
for _, v := range c.executedHookFunc {
v(c)
if c.abort {
return
}
}
}
c.ctx.JSON(http.StatusNoContent, nil)
}
func (c *Ctx[T]) Recover() {
if c.abort {
return
}
id := c.ctx.Param("id")
c.beforeExecuteHook()
db := model.UseDB()
var dbModel T
result := db.Unscoped()
if len(c.gormScopes) > 0 {
result = result.Scopes(c.gormScopes...)
}
var err error
session := result.Session(&gorm.Session{})
if c.table != "" {
err = session.Table(c.table).Take(&dbModel, id).Error
} else {
err = session.First(&dbModel, id).Error
}
if err != nil {
errHandler(c.ctx, err)
return
}
err = result.Model(&dbModel).Update("deleted_at", nil).Error
if err != nil {
errHandler(c.ctx, err)
return
}
if len(c.executedHookFunc) > 0 {
for _, v := range c.executedHookFunc {
v(c)
if c.abort {
return
}
}
}
c.ctx.JSON(http.StatusNoContent, nil)
}

View file

@ -1,23 +0,0 @@
package cosy
import (
"errors"
"github.com/0xJacky/Nginx-UI/internal/logger"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"gorm.io/gorm"
"net/http"
)
func errHandler(c *gin.Context, err error) {
logger.GetLogger().WithOptions(zap.AddCallerSkip(1)).Errorln(err)
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"message": err.Error(),
})
return
}
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
}

View file

@ -1,212 +0,0 @@
package cosy
import (
"fmt"
"github.com/0xJacky/Nginx-UI/internal/logger"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"strings"
)
func (c *Ctx[T]) SetFussy(keys ...string) *Ctx[T] {
c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB {
return QueryToFussySearch(c.ctx, tx, keys...)
})
return c
}
func (c *Ctx[T]) SetFussyKeys(value string, keys ...string) *Ctx[T] {
c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB {
return QueryToFussyKeysSearch(c.ctx, tx, value, keys...)
})
return c
}
func (c *Ctx[T]) SetEqual(keys ...string) *Ctx[T] {
c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB {
return QueryToEqualSearch(c.ctx, tx, keys...)
})
return c
}
func (c *Ctx[T]) SetIn(keys ...string) *Ctx[T] {
c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB {
return QueryToInSearch(c.ctx, tx, keys...)
})
return c
}
func (c *Ctx[T]) SetOrFussy(keys ...string) *Ctx[T] {
c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB {
return QueryToOrFussySearch(c.ctx, tx, keys...)
})
return c
}
func (c *Ctx[T]) SetOrEqual(keys ...string) *Ctx[T] {
c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB {
return QueryToOrEqualSearch(c.ctx, tx, keys...)
})
return c
}
func (c *Ctx[T]) SetOrIn(keys ...string) *Ctx[T] {
c.gormScopes = append(c.gormScopes, func(tx *gorm.DB) *gorm.DB {
return QueryToOrInSearch(c.ctx, tx, keys...)
})
return c
}
func QueryToInSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB {
for _, v := range keys {
queryArray := c.QueryArray(v + "[]")
if len(queryArray) == 0 {
queryArray = c.QueryArray(v)
}
if len(queryArray) == 1 && queryArray[0] == "" {
continue
}
if len(queryArray) >= 1 {
var builder strings.Builder
stmt := db.Statement
stmt.QuoteTo(&builder, clause.Column{Table: stmt.Table, Name: v})
builder.WriteString(" IN ?")
db = db.Where(builder.String(), queryArray)
}
}
return db
}
func QueryToEqualSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB {
for _, v := range keys {
if c.Query(v) != "" {
var sb strings.Builder
_, err := fmt.Fprintf(&sb, "`%s` = ?", v)
if err != nil {
logger.Error(err)
continue
}
db = db.Where(sb.String(), c.Query(v))
}
}
return db
}
func QueryToFussySearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB {
for _, v := range keys {
if c.Query(v) != "" {
var sb strings.Builder
_, err := fmt.Fprintf(&sb, "`%s` LIKE ?", v)
if err != nil {
logger.Error(err)
continue
}
var sbValue strings.Builder
_, err = fmt.Fprintf(&sbValue, "%%%s%%", c.Query(v))
if err != nil {
logger.Error(err)
continue
}
db = db.Where(sb.String(), sbValue.String())
}
}
return db
}
func QueryToFussyKeysSearch(c *gin.Context, db *gorm.DB, value string, keys ...string) *gorm.DB {
if c.Query(value) == "" {
return db
}
var condition *gorm.DB
for i, v := range keys {
sb := v + " LIKE ?"
sv := "%" + c.Query(value) + "%"
switch i {
case 0:
condition = db.Where(db.Where(sb, sv))
default:
condition = condition.Or(sb, sv)
}
}
return db.Where(condition)
}
func QueryToOrInSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB {
for _, v := range keys {
queryArray := c.QueryArray(v + "[]")
if len(queryArray) == 0 {
queryArray = c.QueryArray(v)
}
if len(queryArray) == 1 && queryArray[0] == "" {
continue
}
if len(queryArray) >= 1 {
var sb strings.Builder
_, err := fmt.Fprintf(&sb, "`%s` IN ?", v)
if err != nil {
logger.Error(err)
continue
}
db = db.Or(sb.String(), queryArray)
}
}
return db
}
func QueryToOrEqualSearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB {
for _, v := range keys {
if c.Query(v) != "" {
var sb strings.Builder
_, err := fmt.Fprintf(&sb, "`%s` = ?", v)
if err != nil {
logger.Error(err)
continue
}
db = db.Or(sb.String(), c.Query(v))
}
}
return db
}
func QueryToOrFussySearch(c *gin.Context, db *gorm.DB, keys ...string) *gorm.DB {
for _, v := range keys {
if c.Query(v) != "" {
var sb strings.Builder
_, err := fmt.Fprintf(&sb, "`%s` LIKE ?", v)
if err != nil {
logger.Error(err)
continue
}
var sbValue strings.Builder
_, err = fmt.Fprintf(&sbValue, "%%%s%%", c.Query(v))
if err != nil {
logger.Error(err)
continue
}
db = db.Or(sb.String(), sbValue.String())
}
}
return db
}

View file

@ -1,39 +0,0 @@
package cosy
import "gorm.io/gorm"
func (c *Ctx[T]) GormScope(hook func(tx *gorm.DB) *gorm.DB) *Ctx[T] {
c.gormScopes = append(c.gormScopes, hook)
return c
}
func (c *Ctx[T]) beforeExecuteHook() {
if len(c.beforeExecuteHookFunc) > 0 {
for _, v := range c.beforeExecuteHookFunc {
v(c)
}
}
}
func (c *Ctx[T]) beforeDecodeHook() {
if len(c.beforeDecodeHookFunc) > 0 {
for _, v := range c.beforeDecodeHookFunc {
v(c)
}
}
}
func (c *Ctx[T]) BeforeDecodeHook(hook ...func(ctx *Ctx[T])) *Ctx[T] {
c.beforeDecodeHookFunc = append(c.beforeDecodeHookFunc, hook...)
return c
}
func (c *Ctx[T]) BeforeExecuteHook(hook ...func(ctx *Ctx[T])) *Ctx[T] {
c.beforeExecuteHookFunc = append(c.beforeExecuteHookFunc, hook...)
return c
}
func (c *Ctx[T]) ExecutedHook(hook ...func(ctx *Ctx[T])) *Ctx[T] {
c.executedHookFunc = append(c.executedHookFunc, hook...)
return c
}

View file

@ -1,172 +0,0 @@
package cosy
import (
"github.com/0xJacky/Nginx-UI/internal/logger"
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin"
"github.com/spf13/cast"
"gorm.io/gorm"
"net/http"
)
func GetPagingParams(c *gin.Context) (page, offset, pageSize int) {
page = cast.ToInt(c.Query("page"))
if page == 0 {
page = 1
}
pageSize = settings.ServerSettings.PageSize
reqPageSize := c.Query("page_size")
if reqPageSize != "" {
pageSize = cast.ToInt(reqPageSize)
}
offset = (page - 1) * pageSize
return
}
func (c *Ctx[T]) combineStdSelectorRequest() {
StdSelectorInitID := c.ctx.QueryArray("id[]")
if len(StdSelectorInitID) > 0 {
c.GormScope(func(tx *gorm.DB) *gorm.DB {
return tx.Where(c.itemKey+" IN ?", StdSelectorInitID)
})
}
}
func (c *Ctx[T]) result() (*gorm.DB, bool) {
for _, v := range c.preloads {
t := v
c.GormScope(func(tx *gorm.DB) *gorm.DB {
tx = tx.Preload(t)
return tx
})
}
c.beforeExecuteHook()
var dbModel T
result := model.UseDB()
if cast.ToBool(c.ctx.Query("trash")) {
tableName := c.table
if c.table == "" {
stmt := &gorm.Statement{DB: model.UseDB()}
err := stmt.Parse(&dbModel)
if err != nil {
logger.Error(err)
return nil, false
}
tableName = stmt.Schema.Table
}
result = result.Unscoped().Where(tableName + ".deleted_at IS NOT NULL")
}
result = result.Model(&dbModel)
if c.table != "" {
result = result.Table(c.table, c.tableArgs...)
}
c.combineStdSelectorRequest()
if len(c.gormScopes) > 0 {
result = result.Scopes(c.gormScopes...)
}
return result, true
}
func (c *Ctx[T]) ListAllData() (data any, ok bool) {
result, ok := c.result()
if !ok {
return nil, false
}
result = result.Scopes(c.SortOrder())
if c.scan == nil {
models := make([]*T, 0)
result.Find(&models)
if c.transformer != nil {
transformed := make([]any, 0)
for k := range models {
transformed = append(transformed, c.transformer(models[k]))
}
data = transformed
} else {
data = models
}
} else {
data = c.scan(result)
}
return data, true
}
func (c *Ctx[T]) PagingListData() (*model.DataList, bool) {
result, ok := c.result()
if !ok {
return nil, false
}
scopesResult := result.Scopes(c.OrderAndPaginate())
data := &model.DataList{}
if c.scan == nil {
models := make([]*T, 0)
scopesResult.Find(&models)
if c.transformer != nil {
transformed := make([]any, 0)
for k := range models {
transformed = append(transformed, c.transformer(models[k]))
}
data.Data = transformed
} else {
data.Data = models
}
} else {
data.Data = c.scan(scopesResult)
}
var totalRecords int64
delete(result.Statement.Clauses, "ORDER BY")
delete(result.Statement.Clauses, "LIMIT")
result.Count(&totalRecords)
page := cast.ToInt(c.ctx.Query("page"))
if page == 0 {
page = 1
}
pageSize := settings.ServerSettings.PageSize
if reqPageSize := c.ctx.Query("page_size"); reqPageSize != "" {
pageSize = cast.ToInt(reqPageSize)
}
data.Pagination = model.Pagination{
Total: totalRecords,
PerPage: pageSize,
CurrentPage: page,
TotalPages: model.TotalPage(totalRecords, pageSize),
}
return data, true
}
func (c *Ctx[T]) PagingList() {
data, ok := c.PagingListData()
if ok {
c.ctx.JSON(http.StatusOK, data)
}
}
// EmptyPagingList return empty list
func (c *Ctx[T]) EmptyPagingList() {
pageSize := settings.ServerSettings.PageSize
if reqPageSize := c.ctx.Query("page_size"); reqPageSize != "" {
pageSize = cast.ToInt(reqPageSize)
}
data := &model.DataList{Data: make([]any, 0)}
data.Pagination.PerPage = pageSize
c.ctx.JSON(http.StatusOK, data)
}

View file

@ -1,96 +0,0 @@
package map2struct
import (
"github.com/mitchellh/mapstructure"
"github.com/shopspring/decimal"
"github.com/spf13/cast"
"gopkg.in/guregu/null.v4"
"reflect"
"time"
)
var timeLocation *time.Location
func init() {
timeLocation, _ = time.LoadLocation("Asia/Shanghai")
}
func ToTimeHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if t != reflect.TypeOf(time.Time{}) {
return data, nil
}
switch f.Kind() {
case reflect.String:
return cast.ToTimeInDefaultLocationE(data, timeLocation)
case reflect.Float64:
return time.Unix(0, int64(data.(float64))*int64(time.Millisecond)), nil
case reflect.Int64:
return time.Unix(0, data.(int64)*int64(time.Millisecond)), nil
default:
return data, nil
}
// Convert it by parsing
}
}
func ToTimePtrHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if t != reflect.TypeOf(&time.Time{}) {
return data, nil
}
switch f.Kind() {
case reflect.String:
if data == "" {
return nil, nil
}
v, err := cast.ToTimeInDefaultLocationE(data, timeLocation)
return &v, err
case reflect.Float64:
v := time.Unix(0, int64(data.(float64))*int64(time.Millisecond))
return &v, nil
case reflect.Int64:
v := time.Unix(0, data.(int64)*int64(time.Millisecond))
return &v, nil
default:
return data, nil
}
// Convert it by parsing
}
}
func ToDecimalHookFunc() mapstructure.DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if t == reflect.TypeOf(decimal.Decimal{}) {
if f.Kind() == reflect.Float64 {
return decimal.NewFromFloat(data.(float64)), nil
}
if input := data.(string); input != "" {
return decimal.NewFromString(data.(string))
}
return decimal.Decimal{}, nil
}
return data, nil
}
}
func ToNullableStringHookFunc() mapstructure.DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if t == reflect.TypeOf(null.String{}) {
return null.StringFrom(data.(string)), nil
}
return data, nil
}
}

View file

@ -1,25 +0,0 @@
package map2struct
import (
"github.com/mitchellh/mapstructure"
)
func WeakDecode(input, output interface{}) error {
config := &mapstructure.DecoderConfig{
Metadata: nil,
Result: output,
WeaklyTypedInput: true,
DecodeHook: mapstructure.ComposeDecodeHookFunc(
ToDecimalHookFunc(), ToTimeHookFunc(), ToNullableStringHookFunc(),
ToTimePtrHookFunc(),
),
TagName: "json",
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
return err
}
return decoder.Decode(input)
}

View file

@ -1,46 +0,0 @@
package cosy
import (
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/model"
"gorm.io/gorm"
"net/http"
)
func (c *Ctx[T]) UpdateOrder() {
var json struct {
TargetID int `json:"target_id"`
Direction int `json:"direction" binding:"oneof=-1 1"`
AffectedIDs []int `json:"affected_ids"`
}
if !api.BindAndValid(c.ctx, &json) {
return
}
affectedLen := len(json.AffectedIDs)
db := model.UseDB()
if c.table != "" {
db = db.Table(c.table, c.tableArgs...)
}
// update target
err := db.Model(&c.Model).Where("id = ?", json.TargetID).Update("order_id", gorm.Expr("order_id + ?", affectedLen*(-json.Direction))).Error
if err != nil {
api.ErrHandler(c.ctx, err)
return
}
// update affected
err = db.Model(&c.Model).Where("id in ?", json.AffectedIDs).Update("order_id", gorm.Expr("order_id + ?", json.Direction)).Error
if err != nil {
api.ErrHandler(c.ctx, err)
return
}
c.ctx.JSON(http.StatusOK, json)
}

View file

@ -1,41 +0,0 @@
package cosy
import (
"github.com/0xJacky/Nginx-UI/internal/logger"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"strings"
"sync"
)
func (c *Ctx[T]) SortOrder() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
order := c.ctx.DefaultQuery("order", "desc")
if order != "desc" && order != "asc" {
order = "desc"
}
sortBy := c.ctx.DefaultQuery("sort_by", c.itemKey)
s, _ := schema.Parse(c.Model, &sync.Map{}, schema.NamingStrategy{})
if _, ok := s.FieldsByDBName[sortBy]; !ok && sortBy != c.itemKey {
logger.Error("invalid order field:", sortBy)
return db
}
var sb strings.Builder
sb.WriteString(sortBy)
sb.WriteString(" ")
sb.WriteString(order)
return db.Order(sb.String())
}
}
func (c *Ctx[T]) OrderAndPaginate() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
db = c.SortOrder()(db)
_, offset, pageSize := GetPagingParams(c.ctx)
return db.Offset(offset).Limit(pageSize)
}
}

View file

@ -1,101 +0,0 @@
package cosy
import (
"github.com/0xJacky/Nginx-UI/api/cosy/map2struct"
"github.com/0xJacky/Nginx-UI/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"net/http"
)
func (c *Ctx[T]) SetNextHandler(handler gin.HandlerFunc) *Ctx[T] {
c.nextHandler = &handler
return c
}
func (c *Ctx[T]) Modify() {
if c.abort {
return
}
id := c.ctx.Param("id")
errs := c.validate()
if len(errs) > 0 {
c.ctx.JSON(http.StatusNotAcceptable, gin.H{
"message": "Requested with wrong parameters",
"errors": errs,
})
return
}
db := model.UseDB()
result := db
if len(c.gormScopes) > 0 {
result = result.Scopes(c.gormScopes...)
}
err := result.Session(&gorm.Session{}).First(&c.OriginModel, id).Error
if err != nil {
c.AbortWithError(err)
return
}
c.beforeDecodeHook()
if c.abort {
return
}
var selectedFields []string
for k := range c.Payload {
selectedFields = append(selectedFields, k)
}
err = map2struct.WeakDecode(c.Payload, &c.Model)
if err != nil {
errHandler(c.ctx, err)
return
}
c.beforeExecuteHook()
if c.abort {
return
}
if c.table != "" {
db = db.Table(c.table, c.tableArgs...)
}
err = db.Model(&c.OriginModel).Select(selectedFields).Updates(&c.Model).Error
if err != nil {
c.AbortWithError(err)
return
}
err = db.Preload(clause.Associations).First(&c.Model, id).Error
if err != nil {
c.AbortWithError(err)
return
}
if len(c.executedHookFunc) > 0 {
for _, v := range c.executedHookFunc {
v(c)
if c.abort {
return
}
}
}
if c.nextHandler != nil {
(*c.nextHandler)(c.ctx)
} else {
c.ctx.JSON(http.StatusOK, c.Model)
}
}

View file

@ -2,7 +2,7 @@ package notification
import (
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/api/cosy"
"github.com/0xJacky/Nginx-UI/internal/cosy"
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query"
"github.com/gin-gonic/gin"

View file

@ -2,7 +2,7 @@ package user
import (
"github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/api/cosy"
"github.com/0xJacky/Nginx-UI/internal/cosy"
"github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query"
"github.com/0xJacky/Nginx-UI/settings"