chore: initial commit of ai site platform
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
188
platform/internal/crud/common.go
Normal file
188
platform/internal/crud/common.go
Normal file
@@ -0,0 +1,188 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user