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