chore: initial commit of ai site platform
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
169
platform/internal/crud/memory.go
Normal file
169
platform/internal/crud/memory.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package crud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"aijianzhan/platform/internal/meta"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// MemoryEngine 骨架默认引擎:不依赖外部 DB,方便本地验证动态 CRUD。
|
||||
type MemoryEngine struct {
|
||||
mu sync.RWMutex
|
||||
rows map[string][]map[string]any // schema.table
|
||||
}
|
||||
|
||||
func NewMemoryEngine() *MemoryEngine {
|
||||
return &MemoryEngine{rows: map[string][]map[string]any{}}
|
||||
}
|
||||
|
||||
func tableKey(ref *meta.ResourceRef) string {
|
||||
return ref.App.SchemaName + "." + ref.Entity.Table
|
||||
}
|
||||
|
||||
func (e *MemoryEngine) List(ctx context.Context, ref *meta.ResourceRef, tenantID int64, page, pageSize int, filters map[string]string, sortBy string) ([]map[string]any, int, error) {
|
||||
if err := ensureOp(ref, "list"); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := validateFilters(ref, filters); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := validateSort(ref, sortBy); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
page, pageSize = pageBounds(ref, page, pageSize)
|
||||
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
all := e.rows[tableKey(ref)]
|
||||
filtered := make([]map[string]any, 0, len(all))
|
||||
for _, row := range all {
|
||||
if toInt64(row["tenant_id"]) != tenantID {
|
||||
continue
|
||||
}
|
||||
if !matchOrgScope(row, RowScopeFrom(ctx).OrgUnitIDs) {
|
||||
continue
|
||||
}
|
||||
if matchFilters(row, filters) {
|
||||
filtered = append(filtered, cloneRow(row))
|
||||
}
|
||||
}
|
||||
sortRows(filtered, sortBy)
|
||||
total := len(filtered)
|
||||
start := (page - 1) * pageSize
|
||||
if start >= total {
|
||||
return []map[string]any{}, total, nil
|
||||
}
|
||||
end := start + pageSize
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
return filtered[start:end], total, nil
|
||||
}
|
||||
|
||||
func (e *MemoryEngine) Get(ctx context.Context, ref *meta.ResourceRef, tenantID int64, id string) (map[string]any, error) {
|
||||
if err := ensureOp(ref, "get"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
pk := ref.Entity.PrimaryKey
|
||||
for _, row := range e.rows[tableKey(ref)] {
|
||||
if toInt64(row["tenant_id"]) == tenantID && fmt.Sprint(row[pk]) == id {
|
||||
if !matchOrgScope(row, RowScopeFrom(ctx).OrgUnitIDs) {
|
||||
break
|
||||
}
|
||||
return cloneRow(row), nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
func (e *MemoryEngine) Create(ctx context.Context, ref *meta.ResourceRef, tenantID, userID int64, body map[string]any) (map[string]any, error) {
|
||||
if err := ensureOp(ref, "create"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
row, err := sanitizeBody(ref.Entity, body, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pk := ref.Entity.PrimaryKey
|
||||
if _, ok := row[pk]; !ok {
|
||||
if isAutoPK(ref.Entity) {
|
||||
row[pk] = time.Now().UnixNano()
|
||||
} else {
|
||||
row[pk] = uuid.NewString()
|
||||
}
|
||||
}
|
||||
now := time.Now().UTC().Format(time.RFC3339)
|
||||
row["tenant_id"] = tenantID
|
||||
row["created_by"] = userID
|
||||
row["created_at"] = now
|
||||
row["updated_at"] = now
|
||||
if scope := RowScopeFrom(ctx); scope.WriteOrgUnit > 0 {
|
||||
row["org_unit_id"] = scope.WriteOrgUnit
|
||||
}
|
||||
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
k := tableKey(ref)
|
||||
e.rows[k] = append(e.rows[k], row)
|
||||
return cloneRow(row), nil
|
||||
}
|
||||
|
||||
func (e *MemoryEngine) Update(_ context.Context, ref *meta.ResourceRef, tenantID int64, id string, body map[string]any) (map[string]any, error) {
|
||||
if err := ensureOp(ref, "update"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patch, err := sanitizeBody(ref.Entity, body, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
pk := ref.Entity.PrimaryKey
|
||||
rows := e.rows[tableKey(ref)]
|
||||
for i, row := range rows {
|
||||
if toInt64(row["tenant_id"]) == tenantID && fmt.Sprint(row[pk]) == id {
|
||||
for k, v := range patch {
|
||||
if k == pk || k == "tenant_id" {
|
||||
continue
|
||||
}
|
||||
row[k] = v
|
||||
}
|
||||
row["updated_at"] = time.Now().UTC().Format(time.RFC3339)
|
||||
rows[i] = row
|
||||
return cloneRow(row), nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
func (e *MemoryEngine) Delete(_ context.Context, ref *meta.ResourceRef, tenantID int64, id string) error {
|
||||
if err := ensureOp(ref, "delete"); err != nil {
|
||||
return err
|
||||
}
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
pk := ref.Entity.PrimaryKey
|
||||
k := tableKey(ref)
|
||||
rows := e.rows[k]
|
||||
out := rows[:0]
|
||||
found := false
|
||||
for _, row := range rows {
|
||||
if toInt64(row["tenant_id"]) == tenantID && fmt.Sprint(row[pk]) == id {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
out = append(out, row)
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("not found")
|
||||
}
|
||||
e.rows[k] = out
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user