27 lines
4.3 KiB
JavaScript
27 lines
4.3 KiB
JavaScript
/** Run INSIDE the server using auth-cli.cjs run; no content or credentials are exported. */
|
|
import {readFile,writeFile,mkdir} from 'node:fs/promises';import path from 'node:path';
|
|
const base=process.env.PROMPT_LOCAL_API||'http://127.0.0.1:3000',dir=process.env.PROMPT_COVER_BUNDLE||'/tmp/prompt-document-covers';
|
|
const catalog=JSON.parse(await readFile(path.join(dir,'catalog.json'),'utf8')),stateFile=process.env.PROMPT_COVER_STATE||'/tmp/prompt-document-cover-state.json';
|
|
let state;try{state=JSON.parse(await readFile(stateFile,'utf8'))}catch{state={assets:{},snapshots:{},done:[]}};
|
|
const save=()=>writeFile(stateFile,JSON.stringify(state),{mode:0o600});
|
|
async function api(route,options={}){const r=await fetch(base+route,options);if(!r.ok)throw Error(route+': '+r.status);return r.json()}
|
|
const json=(method,body)=>({method,headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
|
let allTags=await api('/api/tags');if(allTags.filter(t=>t.kind==='system').length!==2)throw Error('System tag migration is not deployed');
|
|
async function tag(name){let t=allTags.find(t=>t.name===name);if(!t){t=await api('/api/tags',json('POST',{name:'#'+name}));allTags.push(t)}return t.id;}
|
|
const sys=key=>{const t=allTags.find(t=>t.system_key===key);if(!t)throw Error('Missing system tag');return t.id};
|
|
async function upload(key,bytes,name){if(state.assets[key])return state.assets[key];const f=new FormData();f.set('kind','image');f.set('assetOnly','true');f.append('files',new Blob([bytes],{type:'image/png'}),name);const d=await api('/api/upload',{method:'POST',body:f});state.assets[key]=d.assets[0];await save();return d.assets[0]}
|
|
async function oldMedia(entry){if(entry.assets)return entry.assets;const assets=[];for(const m of entry.media){if(m.kind!=='image')continue;const u=new URL(m.url,base);if(u.origin!==new URL(base).origin)throw Error('Unexpected media origin');const r=await fetch(u);if(!r.ok)throw Error('Source image unavailable');assets.push({id:await upload(m.url,await r.arrayBuffer(),'original-reference.png'),role:'content'});}return assets;}
|
|
async function read(id){return (await api('/api/notes/'+id)).entry}
|
|
async function snapshot(entry){if(!state.snapshots[entry.id]){state.snapshots[entry.id]=entry;await save()}}
|
|
async function labels(entry,extra=[],system=[]){const ids=[];for(const name of new Set([...entry.categories,...extra]))ids.push(await tag(name));for(const s of entry.systemTags||[])ids.push(s.id);for(const key of system)ids.push(sys(key));return [...new Set(ids)]}
|
|
await mkdir(dir,{recursive:true});
|
|
for(const n of catalog.notes){if(state.done.includes(n.id))continue;const entry=await read(n.id);await snapshot(entry);const cover=await upload('cover:'+n.id,await readFile(path.join(dir,n.file)),n.file),assets=[{id:cover,role:'content'},...await oldMedia(entry)].filter((a,i,arr)=>arr.findIndex(b=>b.id===a.id)===i);
|
|
const extra=n.collection==='照片入皮影'?['中国风']:n.collection==='街头小人'?['贴纸']:[],tagIds=await labels(entry,extra,n.rebrand?['document-entry']:[]),markdown=n.rebrand?entry.editableMarkdown.replaceAll('waterfall-wiki','PROMPT'):entry.editableMarkdown;
|
|
if(entry.postSlug)await api('/api/notes/'+n.id,json('PUT',{title:entry.title,description:entry.description,markdown,tags:tagIds,assets,coverAssetId:cover,revision:entry.revision}));
|
|
else await api('/api/posts',json('POST',{publicId:n.id,slug:n.slug,kind:'article',title:entry.title,description:entry.description,text:markdown,tags:tagIds,assets,coverAssetId:cover}));
|
|
state.done.push(n.id);await save();
|
|
}
|
|
for(const n of [...catalog.roots,...catalog.entries.map(id=>({id}))]){const entry=await read(n.id);await snapshot(entry);await api('/api/notes/'+n.id,json('PATCH',{tags:await labels(entry,n.topic?[n.topic]:[],['document-entry']),revision:entry.revision}));}
|
|
for(const slug of catalog.references){const r=await fetch(base+'/p/'+slug,{redirect:'manual'});const location=r.headers.get('location');if(!location?.startsWith('/explore/'))throw Error('Reference post unavailable');const entry=await read(location.split('/').at(-1));await snapshot(entry);await api('/api/notes/'+entry.id,json('PATCH',{tags:await labels(entry,['贴纸'],['reference-image']),revision:entry.revision}));}
|
|
console.log(JSON.stringify({independentCovers:catalog.notes.length,rootEntries:catalog.roots.length+catalog.entries.length,referencePosts:catalog.references.length,contentExported:false}));
|