189 lines
4.0 KiB
Go
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
|
|
}
|
|
}
|