170 lines
4.2 KiB
Go
170 lines
4.2 KiB
Go
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
|
||
}
|