chore: baseline V8.10.0-dev.1 with v8_workflows type-mapping fix and trade-pro spec
- SiteForge Enterprise V8.10.0-dev.1 upstream source (210 tests) - Fix: v8_workflows._mapped_input now reads both 类型/分类 columns so WXR imports keep post/product types instead of collapsing to page - Docs: trade-pro theme port spec (R3) and implementation plan Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
"""Site-scoped reusable editor component library with versioned snapshots."""
|
||||
import json
|
||||
from fastapi import APIRouter,Body,HTTPException,Request
|
||||
from . import db,enterprise,blueprints
|
||||
from .core import uid
|
||||
|
||||
router=APIRouter(prefix='/api/sites',tags=['Reusable Components'])
|
||||
|
||||
def setup():
|
||||
with db.connect() as c:
|
||||
c.executescript('''CREATE TABLE IF NOT EXISTS reusable_components(
|
||||
id TEXT PRIMARY KEY,site_id TEXT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,version INTEGER NOT NULL DEFAULT 1,block_json TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL);
|
||||
CREATE INDEX IF NOT EXISTS idx_reuse_site ON reusable_components(site_id);
|
||||
''')
|
||||
|
||||
@router.get('/{site_id}/components')
|
||||
def all_components(request:Request,site_id:str):
|
||||
enterprise.require(request,site_id,'viewer')
|
||||
return [{**r,'block':json.loads(r.pop('block_json'))} for r in db.rows(
|
||||
'SELECT * FROM reusable_components WHERE site_id=? ORDER BY updated_at DESC',(site_id,))]
|
||||
|
||||
@router.post('/{site_id}/components')
|
||||
def save_component(request:Request,site_id:str,payload:dict=Body(...)):
|
||||
enterprise.require(request,site_id,'editor')
|
||||
name=str(payload.get('name','')).strip()[:100]
|
||||
if not name:raise HTTPException(400,'组件名称不能为空')
|
||||
try:block=blueprints.validate_blocks([payload.get('block')])[0]
|
||||
except (ValueError,TypeError) as exc:raise HTTPException(400,str(exc))
|
||||
cid=uid()
|
||||
db.insert('INSERT INTO reusable_components VALUES (?,?,?,?,?,?)',
|
||||
(cid,site_id,name,1,json.dumps(block,ensure_ascii=False),db.now()))
|
||||
return {'id':cid,'name':name,'version':1,'block':block}
|
||||
|
||||
@router.put('/{site_id}/components/{component_id}')
|
||||
def update_component(request:Request,site_id:str,component_id:str,payload:dict=Body(...)):
|
||||
enterprise.require(request,site_id,'editor')
|
||||
current=db.one('SELECT * FROM reusable_components WHERE site_id=? AND id=?',(site_id,component_id))
|
||||
if not current:raise HTTPException(404)
|
||||
if payload.get('expected_version')!=current['version']:raise HTTPException(409,'组件已被修改,请重新加载')
|
||||
try:block=blueprints.validate_blocks([payload.get('block')])[0]
|
||||
except (ValueError,TypeError) as exc:raise HTTPException(400,str(exc))
|
||||
db.execute('UPDATE reusable_components SET name=?,block_json=?,version=?,updated_at=? WHERE id=? AND site_id=?',
|
||||
(str(payload.get('name') or current['name'])[:100],json.dumps(block,ensure_ascii=False),current['version']+1,db.now(),component_id,site_id))
|
||||
return {'id':component_id,'version':current['version']+1}
|
||||
|
||||
@router.delete('/{site_id}/components/{component_id}')
|
||||
def delete_component(request:Request,site_id:str,component_id:str):
|
||||
enterprise.require(request,site_id,'editor')
|
||||
if not db.execute('DELETE FROM reusable_components WHERE site_id=? AND id=?',(site_id,component_id)):
|
||||
raise HTTPException(404)
|
||||
return {'deleted':True}
|
||||
Reference in New Issue
Block a user