chore: initial commit of ai site platform
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
217
platform/internal/meta/store.go
Normal file
217
platform/internal/meta/store.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package meta
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"aijianzhan/platform/internal/blueprint"
|
||||
)
|
||||
|
||||
type AppStatus string
|
||||
|
||||
const (
|
||||
StatusDraft AppStatus = "draft" // 在建:已生成蓝图、尚未成功发布
|
||||
StatusValidating AppStatus = "validating"
|
||||
StatusProvisioning AppStatus = "provisioning"
|
||||
StatusPublished AppStatus = "published"
|
||||
StatusFailed AppStatus = "failed"
|
||||
)
|
||||
|
||||
// StatusLabelCN 管理端展示用中文状态。
|
||||
func StatusLabelCN(s AppStatus) string {
|
||||
switch s {
|
||||
case StatusPublished:
|
||||
return "已发布"
|
||||
case StatusFailed:
|
||||
return "失败"
|
||||
case StatusDraft, StatusValidating, StatusProvisioning:
|
||||
return "在建"
|
||||
default:
|
||||
if s == "" {
|
||||
return "未知"
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
}
|
||||
|
||||
// IsBuilding 是否视为在建(含草稿与发布中)。
|
||||
func IsBuilding(s AppStatus) bool {
|
||||
return s == StatusDraft || s == StatusValidating || s == StatusProvisioning
|
||||
}
|
||||
|
||||
type AppRecord struct {
|
||||
AppID string `json:"app_id"`
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
SchemaName string `json:"schema_name"`
|
||||
DatabaseName string `json:"database_name,omitempty"`
|
||||
Engine string `json:"engine"`
|
||||
Status AppStatus `json:"status"`
|
||||
Blueprint *blueprint.Blueprint `json:"blueprint"`
|
||||
DDL []string `json:"ddl,omitempty"`
|
||||
Endpoints []string `json:"endpoints,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type ResourceRef struct {
|
||||
App *AppRecord
|
||||
Entity blueprint.Entity
|
||||
Resource blueprint.APIResource
|
||||
}
|
||||
|
||||
type AppSummary struct {
|
||||
AppID string `json:"app_id"`
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
Status AppStatus `json:"status"`
|
||||
SchemaName string `json:"schema_name,omitempty"`
|
||||
PageCount int `json:"page_count"`
|
||||
EntityCount int `json:"entity_count"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type Store interface {
|
||||
GetBySlug(ctx context.Context, tenantID int64, slug string) (*AppRecord, error)
|
||||
FindPublishedBySlug(ctx context.Context, slug string) (*AppRecord, error)
|
||||
ListByTenant(ctx context.Context, tenantID int64) ([]AppSummary, error)
|
||||
Save(ctx context.Context, app *AppRecord) error
|
||||
ResolveResource(ctx context.Context, tenantID int64, slug, resource string) (*ResourceRef, error)
|
||||
}
|
||||
|
||||
type MemoryStore struct {
|
||||
mu sync.RWMutex
|
||||
apps map[string]*AppRecord // key: tenantID:slug
|
||||
}
|
||||
|
||||
func NewMemoryStore() *MemoryStore {
|
||||
return &MemoryStore{apps: map[string]*AppRecord{}}
|
||||
}
|
||||
|
||||
func key(tenantID int64, slug string) string {
|
||||
return fmt.Sprintf("%d:%s", tenantID, slug)
|
||||
}
|
||||
|
||||
func (s *MemoryStore) GetBySlug(_ context.Context, tenantID int64, slug string) (*AppRecord, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
app, ok := s.apps[key(tenantID, slug)]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("app not found")
|
||||
}
|
||||
return cloneApp(app), nil
|
||||
}
|
||||
|
||||
func (s *MemoryStore) FindPublishedBySlug(_ context.Context, slug string) (*AppRecord, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
var best *AppRecord
|
||||
for _, app := range s.apps {
|
||||
if app == nil || app.Slug != slug || app.Status != StatusPublished {
|
||||
continue
|
||||
}
|
||||
if best == nil || app.UpdatedAt.After(best.UpdatedAt) {
|
||||
best = app
|
||||
}
|
||||
}
|
||||
if best == nil {
|
||||
return nil, fmt.Errorf("app not found")
|
||||
}
|
||||
return cloneApp(best), nil
|
||||
}
|
||||
|
||||
func (s *MemoryStore) ListByTenant(_ context.Context, tenantID int64) ([]AppSummary, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
out := make([]AppSummary, 0)
|
||||
for _, app := range s.apps {
|
||||
if app == nil || app.TenantID != tenantID {
|
||||
continue
|
||||
}
|
||||
out = append(out, summarizeApp(app))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func summarizeApp(app *AppRecord) AppSummary {
|
||||
sum := AppSummary{
|
||||
AppID: app.AppID,
|
||||
Slug: app.Slug,
|
||||
Name: app.Name,
|
||||
Status: app.Status,
|
||||
SchemaName: app.SchemaName,
|
||||
UpdatedAt: app.UpdatedAt,
|
||||
CreatedAt: app.CreatedAt,
|
||||
}
|
||||
if app.Blueprint != nil {
|
||||
sum.PageCount = len(app.Blueprint.Pages)
|
||||
sum.EntityCount = len(app.Blueprint.Entities)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Save(_ context.Context, app *AppRecord) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.apps[key(app.TenantID, app.Slug)] = cloneApp(app)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MemoryStore) ResolveResource(ctx context.Context, tenantID int64, slug, resource string) (*ResourceRef, error) {
|
||||
app, err := s.GetBySlug(ctx, tenantID, slug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resolveResource(app, resource)
|
||||
}
|
||||
|
||||
func resolveResource(app *AppRecord, resource string) (*ResourceRef, error) {
|
||||
if app.Status != StatusPublished {
|
||||
return nil, fmt.Errorf("app not published")
|
||||
}
|
||||
if app.Blueprint == nil {
|
||||
return nil, fmt.Errorf("blueprint missing")
|
||||
}
|
||||
for _, r := range app.Blueprint.Apis.Resources {
|
||||
path := r.Path
|
||||
if len(path) > 0 && path[0] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
if path != resource {
|
||||
continue
|
||||
}
|
||||
for _, e := range app.Blueprint.Entities {
|
||||
if e.Name == r.Entity {
|
||||
return &ResourceRef{App: app, Entity: e, Resource: r}, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("entity missing for resource")
|
||||
}
|
||||
return nil, fmt.Errorf("resource not found")
|
||||
}
|
||||
|
||||
func cloneApp(app *AppRecord) *AppRecord {
|
||||
if app == nil {
|
||||
return nil
|
||||
}
|
||||
cp := *app
|
||||
if app.Blueprint != nil {
|
||||
raw, _ := json.Marshal(app.Blueprint)
|
||||
var bp blueprint.Blueprint
|
||||
_ = json.Unmarshal(raw, &bp)
|
||||
cp.Blueprint = &bp
|
||||
}
|
||||
if app.DDL != nil {
|
||||
cp.DDL = append([]string{}, app.DDL...)
|
||||
}
|
||||
if app.Endpoints != nil {
|
||||
cp.Endpoints = append([]string{}, app.Endpoints...)
|
||||
}
|
||||
return &cp
|
||||
}
|
||||
Reference in New Issue
Block a user