Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e7e4829db |
@@ -40,6 +40,13 @@ function withParam(url,key,value){var next=new URL(url);next.searchParams.set(ke
|
||||
function fallbackCopy(text){var ta=document.createElement('textarea');ta.value=text;ta.style.position='fixed';ta.style.left='-9999px';ta.style.opacity='0';document.body.appendChild(ta);ta.focus();ta.select();var ok=document.execCommand('copy');ta.remove();if(!ok)throw new Error('复制失败');}
|
||||
async function copyText(text){if(navigator.clipboard&&navigator.clipboard.writeText){try{await navigator.clipboard.writeText(text);return;}catch(e){}}fallbackCopy(text);}
|
||||
function workspaceRelativePath(){var parts=location.pathname.split('/').filter(Boolean);if(parts[0]==='MindSpace'&&parts.length>=3)return parts.slice(2).join('/');return'';}
|
||||
function redirectToMindSpaceLogin(){
|
||||
var returnTo=encodeURIComponent(cleanUrl());
|
||||
var inWechat=/MicroMessenger/i.test(navigator.userAgent||'');
|
||||
if(inWechat){location.href='/auth/wechat/authorize?intent=login&return_to='+returnTo;return;}
|
||||
location.href='/?return_to='+returnTo;
|
||||
}
|
||||
function handleUnauthorizedResponse(){redirectToMindSpaceLogin();return new Error('正在跳转登录…');}
|
||||
function showPlazaView(name){Object.keys(plazaViews).forEach(function(key){var view=plazaViews[key];if(view)view.hidden=key!==name;});}
|
||||
function openPlazaDialog(){if(!dialog)return;dialog.hidden=false;showPlazaView('loading');void refreshPlazaDialog();}
|
||||
function closePlazaDialog(){if(dialog)dialog.hidden=true;showPlazaView('confirm');}
|
||||
@@ -55,7 +62,7 @@ try{
|
||||
var res=await fetch('/api/mindspace/v1/pages/quick-plaza-from-public-html/status?relative_path='+encodeURIComponent(relativePath),{credentials:'same-origin'});
|
||||
var payload=await res.json().catch(function(){return{};});
|
||||
if(!res.ok){
|
||||
if(res.status===401)throw new Error('请先登录 MindSpace');
|
||||
if(res.status===401)throw handleUnauthorizedResponse();
|
||||
var errBody=apiErrorBody(payload);
|
||||
throw new Error(errBody.message||'无法检查发布状态');
|
||||
}
|
||||
@@ -87,7 +94,7 @@ try{
|
||||
var res=await fetch('/api/mindspace/v1/pages/quick-plaza-from-public-html',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json'},body:JSON.stringify({relative_path:relativePath})});
|
||||
var payload=await res.json().catch(function(){return{};});
|
||||
if(!res.ok){
|
||||
if(res.status===401)throw new Error('请先登录 MindSpace');
|
||||
if(res.status===401)throw handleUnauthorizedResponse();
|
||||
var errBody=apiErrorBody(payload);
|
||||
var plazaUrl=resolvePlazaUrl(payload);
|
||||
if(errBody.code==='ALREADY_PUBLISHED'){showAlreadyPublished(plazaUrl);setStatus('该内容已在广场发布',false,true);return;}
|
||||
@@ -143,7 +150,7 @@ try{
|
||||
var res=await fetch('/api/mindspace/v1/pages/quick-wechat-draft-from-public-html/status?relative_path='+encodeURIComponent(relativePath),{credentials:'same-origin'});
|
||||
var payload=await res.json().catch(function(){return{};});
|
||||
if(!res.ok){
|
||||
if(res.status===401)throw new Error('请先登录 MindSpace');
|
||||
if(res.status===401)throw handleUnauthorizedResponse();
|
||||
var errBody=apiErrorBody(payload);
|
||||
throw new Error(errBody.message||'无法检查公众号草稿状态');
|
||||
}
|
||||
@@ -177,7 +184,7 @@ try{
|
||||
var res=await fetch('/api/mindspace/v1/pages/quick-wechat-draft-from-public-html',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json'},body:JSON.stringify({relative_path:relativePath})});
|
||||
var payload=await res.json().catch(function(){return{};});
|
||||
if(!res.ok){
|
||||
if(res.status===401)throw new Error('请先登录 MindSpace');
|
||||
if(res.status===401)throw handleUnauthorizedResponse();
|
||||
var errBody=apiErrorBody(payload);
|
||||
throw new Error(errBody.message||'推送失败');
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ test('injectPublicFileShareButton adds plaza entry and confirm dialog', () => {
|
||||
assert.doesNotMatch(result.html, /data-mindspace-public-share-dialog-panel"/);
|
||||
assert.match(result.html, /color:#fff !important/);
|
||||
assert.match(result.html, /apiErrorBody/);
|
||||
assert.match(result.html, /redirectToMindSpaceLogin/);
|
||||
assert.match(result.html, /auth\/wechat\/authorize\?intent=login/);
|
||||
assert.match(result.html, /ALREADY_PUBLISHED/);
|
||||
assert.equal(result.scriptHashes.length, 1);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Repair icyxu (清风徐徐女士) WeChat pages missing MindSpace records/publications.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/repair-icyxu-wechat-pages-103.mjs [--apply]
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createDbPool } from '../db.mjs';
|
||||
import { createAssetService } from '../mindspace-assets.mjs';
|
||||
import { createPageService } from '../mindspace-pages.mjs';
|
||||
import { createPageSyncService } from '../mindspace-page-sync-service.mjs';
|
||||
import { createPublicationService } from '../mindspace-publications.mjs';
|
||||
import { createWorkspacePageDeliverService } from '../mindspace-workspace-page-deliver.mjs';
|
||||
import { resolveMindSpaceStorageRoot } from '../mindspace-runtime-config.mjs';
|
||||
|
||||
const repoRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
||||
function loadEnvFile(filePath) {
|
||||
if (!fs.existsSync(filePath)) return;
|
||||
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
const eq = trimmed.indexOf('=');
|
||||
if (eq < 0) continue;
|
||||
const key = trimmed.slice(0, eq).trim();
|
||||
const value = trimmed.slice(eq + 1).trim();
|
||||
if (!process.env[key]) process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
loadEnvFile(path.join(repoRoot, '.env'));
|
||||
loadEnvFile(path.join(repoRoot, '.env.local'));
|
||||
loadEnvFile(path.join(repoRoot, '../../.env.local'));
|
||||
|
||||
const USER_ID = '7a650359-5f2e-4911-85ad-cbc74c0cdb22';
|
||||
const TARGET_PATHS = ['public/poem.html', 'public/mountain-trip.html'];
|
||||
|
||||
const apply = process.argv.includes('--apply');
|
||||
const h5Root = process.env.H5_ROOT || repoRoot;
|
||||
const storageRoot = resolveMindSpaceStorageRoot(h5Root, process.env);
|
||||
|
||||
async function main() {
|
||||
const pool = createDbPool();
|
||||
const pageService = createPageService(pool, { h5Root, storageRoot });
|
||||
const assetService = createAssetService(pool, { h5Root, storageRoot });
|
||||
const pageSyncService = createPageSyncService({
|
||||
pool,
|
||||
pageService,
|
||||
assetService,
|
||||
h5Root,
|
||||
syncWorkspaceAssets: assetService.syncWorkspaceAssets?.bind(assetService),
|
||||
});
|
||||
const publicationService = createPublicationService(pool, { h5Root, storageRoot });
|
||||
const workspacePageDeliveryService = createWorkspacePageDeliverService({
|
||||
pool,
|
||||
pageService,
|
||||
publicationService,
|
||||
pageSyncService,
|
||||
h5Root,
|
||||
storageRoot,
|
||||
findPageByRelativePath: pageService.findPageByRelativePath.bind(pageService),
|
||||
});
|
||||
|
||||
const actions = [];
|
||||
for (const relativePath of TARGET_PATHS) {
|
||||
const absPath = path.join(h5Root, 'MindSpace', USER_ID, relativePath);
|
||||
actions.push({
|
||||
relativePath,
|
||||
exists: fs.existsSync(absPath),
|
||||
size: fs.existsSync(absPath) ? fs.statSync(absPath).size : 0,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(JSON.stringify({ userId: USER_ID, dryRun: !apply, targets: actions }, null, 2));
|
||||
|
||||
if (!apply) {
|
||||
console.log('\nDry run only. Re-run with --apply to sync and publish.');
|
||||
await pool.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const syncResult = await pageSyncService.syncUserGeneratedPages(USER_ID, {
|
||||
onlyRelativePaths: TARGET_PATHS,
|
||||
});
|
||||
console.log('sync:', JSON.stringify(syncResult));
|
||||
|
||||
const publishResult = await workspacePageDeliveryService.ensureWorkspaceHtmlPublications(
|
||||
USER_ID,
|
||||
{ onlyRelativePaths: TARGET_PATHS },
|
||||
);
|
||||
console.log('publish:', JSON.stringify(publishResult));
|
||||
|
||||
for (const relativePath of TARGET_PATHS) {
|
||||
const page = await pageService.findPageByRelativePath(USER_ID, relativePath);
|
||||
const publication = page?.id
|
||||
? await publicationService.getCurrent(USER_ID, page.id)
|
||||
: null;
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
relativePath,
|
||||
pageId: page?.id ?? null,
|
||||
pageStatus: page?.status ?? null,
|
||||
publicationId: publication?.id ?? null,
|
||||
publicationStatus: publication?.status ?? null,
|
||||
publicUrl: publication?.publicUrl ?? publication?.public_url ?? null,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await pool.end();
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.stack ?? error.message : error);
|
||||
process.exit(1);
|
||||
});
|
||||
+18
-4
@@ -637,6 +637,7 @@ async function bootstrapUserAuth() {
|
||||
apiSecret: API_SECRET,
|
||||
mindSpacePages,
|
||||
mindSpacePageLiveEdit,
|
||||
syncUserGeneratedPages,
|
||||
healthChannelStore,
|
||||
healthObservationStore: healthDataRuntime.observationStore,
|
||||
healthObservationService: healthDataRuntime.observationService,
|
||||
@@ -1288,11 +1289,21 @@ async function listSessionPublicHtmlRelativePaths(userId, sessionId, { sinceMs =
|
||||
.filter((relativePath) => relativePath?.startsWith('public/') && relativePath.toLowerCase().endsWith('.html')))];
|
||||
}
|
||||
|
||||
async function syncUserGeneratedPages(userId, { sessionId = null, sinceMs = null } = {}) {
|
||||
async function syncUserGeneratedPages(
|
||||
userId,
|
||||
{ sessionId = null, sinceMs = null, onlyRelativePaths = null } = {},
|
||||
) {
|
||||
if (!userId) return;
|
||||
// Agent-run/Finish delivery must stay scoped to the current conversation.
|
||||
// A stale Page Data page elsewhere in the user's workspace must not turn a
|
||||
// successfully completed current task into a failed run.
|
||||
const explicitRelativePaths = Array.isArray(onlyRelativePaths)
|
||||
? [...new Set(
|
||||
onlyRelativePaths
|
||||
.map((relativePath) => normalizeWorkspaceRelativePath(relativePath))
|
||||
.filter(Boolean),
|
||||
)]
|
||||
: null;
|
||||
const discoveredRelativePaths = sessionId
|
||||
? await listSessionPublicHtmlRelativePaths(userId, sessionId, { sinceMs })
|
||||
: null;
|
||||
@@ -1302,6 +1313,7 @@ async function syncUserGeneratedPages(userId, { sessionId = null, sinceMs = null
|
||||
// block an unrelated delivery.
|
||||
const recentWorkspaceRelativePaths =
|
||||
sessionId &&
|
||||
!explicitRelativePaths?.length &&
|
||||
!discoveredRelativePaths?.length &&
|
||||
mindSpaceWorkspacePublicationDelivery
|
||||
? (
|
||||
@@ -1312,9 +1324,11 @@ async function syncUserGeneratedPages(userId, { sessionId = null, sinceMs = null
|
||||
})
|
||||
)?.relativePaths ?? []
|
||||
: [];
|
||||
const pageDataRelativePaths = sessionId
|
||||
? (discoveredRelativePaths?.length ? discoveredRelativePaths : recentWorkspaceRelativePaths)
|
||||
: null;
|
||||
const pageDataRelativePaths = explicitRelativePaths?.length
|
||||
? explicitRelativePaths
|
||||
: sessionId
|
||||
? (discoveredRelativePaths?.length ? discoveredRelativePaths : recentWorkspaceRelativePaths)
|
||||
: null;
|
||||
if (workspacePageDeliver?.syncAndDeliver) {
|
||||
return await workspacePageDeliver.syncAndDeliver(userId, { pageDataRelativePaths });
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ export async function bootstrapPortalIntegrationServices({
|
||||
apiSecret,
|
||||
mindSpacePages,
|
||||
mindSpacePageLiveEdit,
|
||||
syncUserGeneratedPages = null,
|
||||
logger = console,
|
||||
healthChannelStore = null,
|
||||
healthObservationStore = null,
|
||||
@@ -212,6 +213,35 @@ export async function bootstrapPortalIntegrationServices({
|
||||
sessionId,
|
||||
artifacts = [],
|
||||
}) => {
|
||||
const relativePaths = [
|
||||
...new Set(
|
||||
artifacts
|
||||
.map((artifact) =>
|
||||
String(
|
||||
artifact?.relativePath ??
|
||||
artifact?.relative_path ??
|
||||
'',
|
||||
).trim(),
|
||||
)
|
||||
.filter(Boolean),
|
||||
),
|
||||
];
|
||||
if (
|
||||
typeof syncUserGeneratedPages === 'function' &&
|
||||
userId
|
||||
) {
|
||||
void syncUserGeneratedPages(userId, {
|
||||
sessionId,
|
||||
sinceMs: Date.now() - 10 * 60 * 1000,
|
||||
onlyRelativePaths:
|
||||
relativePaths.length > 0 ? relativePaths : null,
|
||||
}).catch((error) => {
|
||||
logger.warn?.(
|
||||
'[WeChat MP] page sync after generation failed:',
|
||||
error instanceof Error ? error.message : error,
|
||||
);
|
||||
});
|
||||
}
|
||||
for (const artifact of artifacts) {
|
||||
const pageOwner =
|
||||
(await userAuth
|
||||
|
||||
@@ -129,6 +129,10 @@ function createSetup(overrides = {}) {
|
||||
apiSecret: 'secret',
|
||||
mindSpacePages: { id: 'pages' },
|
||||
mindSpacePageLiveEdit: { id: 'live-edit' },
|
||||
syncUserGeneratedPages(userId, options) {
|
||||
calls.push(['sync-user-pages', userId, options]);
|
||||
return Promise.resolve({ created: 1, updated: 0, skipped: 0 });
|
||||
},
|
||||
logger: {
|
||||
log(...args) {
|
||||
calls.push(['log', ...args]);
|
||||
@@ -407,6 +411,13 @@ test('preserves generated-page analytics projection', async () => {
|
||||
.length,
|
||||
1,
|
||||
);
|
||||
const syncCall = setup.calls.find(
|
||||
([name]) => name === 'sync-user-pages',
|
||||
);
|
||||
assert.ok(syncCall);
|
||||
assert.equal(syncCall[1], 'user-1');
|
||||
assert.equal(syncCall[2].sessionId, 'session-1');
|
||||
assert.deepEqual(syncCall[2].onlyRelativePaths, ['public/page.html']);
|
||||
const analyticsCall = setup.calls.find(
|
||||
([name]) => name === 'analytics',
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user