"""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}