8cea7dee20
- 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>
54 lines
2.9 KiB
Python
54 lines
2.9 KiB
Python
"""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}
|