Files
ai_site/platform/internal/crud/common.go
2026-07-31 10:31:17 +08:00

189 lines
4.0 KiB
Go

package crud
import (
"fmt"
"sort"
"strconv"
"strings"
"aijianzhan/platform/internal/blueprint"
"aijianzhan/platform/internal/meta"
)
func ensureOp(ref *meta.ResourceRef, op string) error {
for _, o := range ref.Resource.Operations {
if o == op {
return nil
}
}
return fmt.Errorf("operation %s not allowed", op)
}
func validateFilters(ref *meta.ResourceRef, filters map[string]string) error {
if len(filters) == 0 {
return nil
}
allowed := map[string]struct{}{}
if ref.Resource.List != nil {
for _, f := range ref.Resource.List.AllowedFilters {
allowed[f] = struct{}{}
}
}
for k := range filters {
if _, ok := allowed[k]; !ok {
return fmt.Errorf("filter not allowed: %s", k)
}
}
return nil
}
func validateSort(ref *meta.ResourceRef, sortBy string) error {
if sortBy == "" {
return nil
}
field := strings.TrimPrefix(sortBy, "-")
allowed := map[string]struct{}{}
if ref.Resource.List != nil {
for _, s := range ref.Resource.List.AllowedSorts {
allowed[s] = struct{}{}
}
}
if _, ok := allowed[field]; !ok {
return fmt.Errorf("sort not allowed: %s", field)
}
return nil
}
func sanitizeBody(entity blueprint.Entity, body map[string]any, partial bool) (map[string]any, error) {
fields := map[string]blueprint.Field{}
for _, f := range entity.Fields {
fields[f.Name] = f
}
out := map[string]any{}
for k, v := range body {
if k == "tenant_id" || k == "org_unit_id" || k == "created_at" || k == "updated_at" || k == "created_by" {
continue
}
f, ok := fields[k]
if !ok {
return nil, fmt.Errorf("unknown field: %s", k)
}
if f.Name == entity.PrimaryKey && partial {
continue
}
out[k] = v
}
if !partial {
system := map[string]struct{}{
"tenant_id": {}, "org_unit_id": {}, "created_at": {}, "updated_at": {}, "created_by": {},
}
for _, f := range entity.Fields {
if f.Name == entity.PrimaryKey {
continue
}
if _, ok := system[f.Name]; ok {
continue
}
if _, ok := out[f.Name]; !ok && !blueprint.BoolOr(f.Nullable, true) && f.Default == nil {
return nil, fmt.Errorf("missing required field: %s", f.Name)
}
}
}
return out, nil
}
func fieldByName(entity blueprint.Entity, name string) (blueprint.Field, bool) {
for _, f := range entity.Fields {
if f.Name == name {
return f, true
}
}
return blueprint.Field{}, false
}
func isAutoPK(entity blueprint.Entity) bool {
f, ok := fieldByName(entity, entity.PrimaryKey)
if !ok {
return false
}
return f.Type == "bigint" || f.Type == "int"
}
func quoteIdent(name string) string {
return `"` + strings.ReplaceAll(name, `"`, ``) + `"`
}
func qualifiedTable(ref *meta.ResourceRef) string {
return quoteIdent(ref.App.SchemaName) + "." + quoteIdent(ref.Entity.Table)
}
func pageBounds(ref *meta.ResourceRef, page, pageSize int) (int, int) {
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 20
}
if ref.Resource.List != nil && ref.Resource.List.MaxPageSize > 0 && pageSize > ref.Resource.List.MaxPageSize {
pageSize = ref.Resource.List.MaxPageSize
}
return page, pageSize
}
func matchFilters(row map[string]any, filters map[string]string) bool {
for k, v := range filters {
if fmt.Sprint(row[k]) != v {
return false
}
}
return true
}
func sortRows(rows []map[string]any, sortBy string) {
if sortBy == "" {
return
}
desc := strings.HasPrefix(sortBy, "-")
field := strings.TrimPrefix(sortBy, "-")
sort.SliceStable(rows, func(i, j int) bool {
a, b := fmt.Sprint(rows[i][field]), fmt.Sprint(rows[j][field])
if desc {
return a > b
}
return a < b
})
}
func cloneRow(in map[string]any) map[string]any {
out := make(map[string]any, len(in))
for k, v := range in {
out[k] = v
}
return out
}
func toInt64(v any) int64 {
switch t := v.(type) {
case int64:
return t
case int:
return int64(t)
case float64:
return int64(t)
case string:
n, _ := strconv.ParseInt(t, 10, 64)
return n
default:
return 0
}
}
func normalizeDBValue(v any) any {
switch t := v.(type) {
case []byte:
return string(t)
default:
return t
}
}