feat: Z36 host_meta-only publish + Z37 SyncPage LWW UX
Allow display-name-only publish via host_meta; stop treating company conflicts 403 as a fault and ship rebuilt web dist. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -229,6 +229,9 @@ func (l *PublishLogic) Publish(slug string, req *types.PublishReq) (*types.Publi
|
||||
bp.Apis.BasePath = "/api/v1/apps/" + slug
|
||||
mergeRes, err = blueprint.MergeInto(bp, incoming)
|
||||
if err != nil {
|
||||
if isNothingNewToPublish(err) {
|
||||
return l.handleNoBlueprintDelta(existing, req, slug, tenantID, userID)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
publishMode = "pages_added"
|
||||
@@ -249,6 +252,9 @@ func (l *PublishLogic) Publish(slug string, req *types.PublishReq) (*types.Publi
|
||||
bp.Apis.BasePath = "/api/v1/apps/" + slug
|
||||
mergeRes, err = blueprint.MergeInto(bp, incoming)
|
||||
if err != nil {
|
||||
if isNothingNewToPublish(err) {
|
||||
return l.handleNoBlueprintDelta(existing, req, slug, tenantID, userID)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
publishMode = "pages_added"
|
||||
@@ -258,6 +264,13 @@ func (l *PublishLogic) Publish(slug string, req *types.PublishReq) (*types.Publi
|
||||
}
|
||||
}
|
||||
|
||||
// Z36:有蓝图增量时也把 host_meta.module_name 落到蓝图/模块显示名
|
||||
if req.HostMeta != nil {
|
||||
if n := strings.TrimSpace(req.HostMeta.ModuleName); n != "" {
|
||||
bp.Meta.Name = n
|
||||
}
|
||||
}
|
||||
|
||||
bp.EnsureDefaultImportExport()
|
||||
if err := bp.Validate(slug); err != nil {
|
||||
return nil, err
|
||||
@@ -483,6 +496,112 @@ func (l *PublishLogic) audit(action, detail string) error {
|
||||
return l.svcCtx.Audit.Log(l.ctx, authx.TenantID(l.ctx), authx.UserID(l.ctx), action, detail)
|
||||
}
|
||||
|
||||
func isNothingNewToPublish(err error) bool {
|
||||
return err != nil && strings.Contains(err.Error(), "nothing new to publish")
|
||||
}
|
||||
|
||||
// handleNoBlueprintDelta Z36:merge 无增量时,有 module_name 则仅更新 host_meta;否则中文业务错误。
|
||||
func (l *PublishLogic) handleNoBlueprintDelta(
|
||||
existing *meta.AppRecord,
|
||||
req *types.PublishReq,
|
||||
slug string,
|
||||
tenantID, userID int64,
|
||||
) (*types.PublishResp, error) {
|
||||
name := ""
|
||||
if req.HostMeta != nil {
|
||||
name = strings.TrimSpace(req.HostMeta.ModuleName)
|
||||
}
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("没有可发布的蓝图增量(页面/实体/接口未变化)。若仅修改模块显示名,请在 host_meta.module_name 传入新名称后重试 [NO_BLUEPRINT_DELTA]")
|
||||
}
|
||||
return l.publishHostMetaOnly(existing, req, slug, tenantID, userID, name)
|
||||
}
|
||||
|
||||
// publishHostMetaOnly 无蓝图增量时只更新模块显示名并重签胶囊,不跑 DDL。
|
||||
func (l *PublishLogic) publishHostMetaOnly(
|
||||
existing *meta.AppRecord,
|
||||
req *types.PublishReq,
|
||||
slug string,
|
||||
tenantID, userID int64,
|
||||
moduleName string,
|
||||
) (*types.PublishResp, error) {
|
||||
if existing == nil || existing.Blueprint == nil {
|
||||
return nil, fmt.Errorf("existing app has no blueprint")
|
||||
}
|
||||
bp := existing.Blueprint
|
||||
bp.Meta.Name = moduleName
|
||||
bp.Meta.Slug = slug
|
||||
if bp.Apis.BasePath == "" {
|
||||
bp.Apis.BasePath = "/api/v1/apps/" + slug
|
||||
}
|
||||
|
||||
rec := existing
|
||||
rec.Name = moduleName
|
||||
rec.Blueprint = bp
|
||||
rec.UpdatedAt = time.Now().UTC()
|
||||
if rec.Status == "" {
|
||||
rec.Status = meta.StatusPublished
|
||||
}
|
||||
if err := l.svcCtx.Meta.Save(l.ctx, rec); err != nil {
|
||||
return nil, fmt.Errorf("meta save: %w", err)
|
||||
}
|
||||
_ = l.audit("publish.host_meta", audit.DetailJSON(map[string]any{
|
||||
"slug": slug, "module_name": moduleName, "user_id": userID, "publish_mode": "host_meta_updated",
|
||||
}))
|
||||
|
||||
ownerID := authx.AgentID(l.ctx)
|
||||
if ownerID <= 0 {
|
||||
ownerID = userID
|
||||
}
|
||||
secret := l.svcCtx.Config.Agent.CapsuleSecret
|
||||
if secret == "" {
|
||||
secret = l.svcCtx.JWT.AccessSecret
|
||||
}
|
||||
accessPath := ""
|
||||
accessURL := ""
|
||||
if secret != "" && ownerID > 0 {
|
||||
_, filePath, err := agentcap.SealModulePath(secret, tenantID, ownerID, slug)
|
||||
if err == nil {
|
||||
accessPath = filePath
|
||||
base := strings.TrimRight(l.svcCtx.Config.PublicBaseURL, "/")
|
||||
if req.HostMeta != nil && strings.TrimSpace(req.HostMeta.HostBaseURL) != "" {
|
||||
accessURL = strings.TrimRight(strings.TrimSpace(req.HostMeta.HostBaseURL), "/")
|
||||
} else if base != "" {
|
||||
accessURL = base + "/api/v1/public/" + filePath + "/blueprint"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishStyle := ""
|
||||
if req.HostMeta != nil {
|
||||
publishStyle = strings.TrimSpace(req.HostMeta.PublishStyle)
|
||||
}
|
||||
if publishStyle == "" {
|
||||
publishStyle = "immediate"
|
||||
}
|
||||
|
||||
endpoints := rec.Endpoints
|
||||
if len(endpoints) == 0 {
|
||||
endpoints = buildEndpoints(bp)
|
||||
}
|
||||
return &types.PublishResp{
|
||||
AppID: rec.AppID,
|
||||
Slug: slug,
|
||||
SchemaName: rec.SchemaName,
|
||||
DatabaseName: rec.DatabaseName,
|
||||
Status: string(rec.Status),
|
||||
Endpoints: endpoints,
|
||||
MemoryMode: l.svcCtx.MemoryMode,
|
||||
PublishMode: "host_meta_updated",
|
||||
ModuleName: moduleName,
|
||||
PublishStyle: publishStyle,
|
||||
AccessPath: accessPath,
|
||||
AccessURL: accessURL,
|
||||
PublishedAt: rec.UpdatedAt.UTC().Format(time.RFC3339),
|
||||
OwnerID: ownerID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildEndpoints(bp *blueprint.Blueprint) []string {
|
||||
base := strings.TrimRight(bp.Apis.BasePath, "/")
|
||||
if base == "" {
|
||||
|
||||
@@ -29,7 +29,7 @@ type PublishResp struct {
|
||||
Endpoints []string `json:"endpoints"`
|
||||
DDL []string `json:"ddl,omitempty"`
|
||||
MemoryMode bool `json:"memory_mode"`
|
||||
PublishMode string `json:"publish_mode"` // created | pages_added | replaced
|
||||
PublishMode string `json:"publish_mode"` // created | pages_added | replaced | host_meta_updated
|
||||
AddedPages []string `json:"added_pages,omitempty"`
|
||||
AddedEntities []string `json:"added_entities,omitempty"`
|
||||
AddedResources []string `json:"added_resources,omitempty"`
|
||||
|
||||
Reference in New Issue
Block a user