guanghulab/scripts/skyeye/dev-portal-notion-sync.js

194 lines
6.2 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
// scripts/skyeye/dev-portal-notion-sync.js
// 开发者门户 · Notion 动态同步
//
// ═══════════════════════════════════════════════
// 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
// 📜 Copyright: 国作登字-2026-A-00037559
// ═══════════════════════════════════════════════
//
// 目的:将开发者频道部署动态同步到 Notion 画像数据库
// 铁律(与 write-receipt-to-notion.js 一致):
// 1. 写入失败不阻塞主流程
// 2. 同一事件幂等
// 3. 失败时写入本地 SYSLOG
//
// 环境变量:
// NOTION_API_KEY — Notion API 密钥(必须)
// DEV_PROFILE_DB_ID — 开发者画像数据库 ID必须
//
// 参数:
// --dev-ids "DEV-001,DEV-002" — 涉及的开发者编号
// --action "channel_deploy" — 操作类型
// --timestamp "ISO-8601" — 时间戳
'use strict';
const fs = require('fs');
const path = require('path');
const https = require('https');
const NOTION_API_KEY = process.env.NOTION_API_KEY || '';
const DEV_PROFILE_DB_ID = process.env.DEV_PROFILE_DB_ID || '';
const ROOT = path.resolve(__dirname, '../..');
const SYSLOG_DIR = path.join(ROOT, 'data/neural-reports/syslog');
// ━━━ 参数解析 ━━━
function parseArgs() {
const args = process.argv.slice(2);
const result = { devIds: [], action: 'unknown', timestamp: new Date().toISOString() };
for (let i = 0; i < args.length; i++) {
if (args[i] === '--dev-ids' && args[i + 1]) {
result.devIds = args[++i].split(',').filter(Boolean);
} else if (args[i] === '--action' && args[i + 1]) {
result.action = args[++i];
} else if (args[i] === '--timestamp' && args[i + 1]) {
result.timestamp = args[++i];
}
}
return result;
}
// ━━━ 加载门禁配置获取开发者信息 ━━━
function loadDevInfo(devId) {
try {
const configPath = path.join(ROOT, '.github/persona-brain/gate-guard-config.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
const dev = config.developer_permissions && config.developer_permissions[devId];
return dev || null;
} catch (e) {
return null;
}
}
// ━━━ Notion API 请求 ━━━
function notionRequest(endpoint, body) {
return new Promise(function(resolve, reject) {
if (!NOTION_API_KEY) {
reject(new Error('NOTION_API_KEY 未配置'));
return;
}
const data = JSON.stringify(body);
const options = {
hostname: 'api.notion.com',
path: endpoint,
method: 'POST',
headers: {
'Authorization': 'Bearer ' + NOTION_API_KEY,
'Notion-Version': '2022-06-28',
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data)
}
};
const req = https.request(options, function(res) {
let body = '';
res.on('data', function(chunk) { body += chunk; });
res.on('end', function() {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(JSON.parse(body));
} else {
reject(new Error('Notion API ' + res.statusCode + ': ' + body.slice(0, 200)));
}
});
});
req.on('error', reject);
req.setTimeout(15000, function() { req.destroy(new Error('Notion API timeout')); });
req.write(data);
req.end();
});
}
// ━━━ 写入 SYSLOG ━━━
function writeSyslog(entry) {
try {
if (!fs.existsSync(SYSLOG_DIR)) fs.mkdirSync(SYSLOG_DIR, { recursive: true });
const date = new Date().toISOString().slice(0, 10);
const logPath = path.join(SYSLOG_DIR, 'dev-portal-sync-' + date + '.jsonl');
fs.appendFileSync(logPath, JSON.stringify(entry) + '\n');
} catch (e) {
console.error('SYSLOG写入失败:', e.message);
}
}
// ━━━ 同步单个开发者动态到 Notion ━━━
async function syncDevActivity(devId, action, timestamp) {
const devInfo = loadDevInfo(devId);
const properties = {
'开发者编号': { title: [{ text: { content: devId } }] },
'开发者姓名': { rich_text: [{ text: { content: devInfo ? devInfo.name : devId } }] },
'人格体编号': { rich_text: [{ text: { content: devInfo ? devInfo.persona_id : '—' } }] },
'操作类型': { select: { name: action } },
'操作时间': { date: { start: timestamp } },
'门户频道': { rich_text: [{ text: { content: 'channels/' + devId + '/' } }] }
};
try {
await notionRequest('/v1/pages', {
parent: { database_id: DEV_PROFILE_DB_ID },
properties: properties
});
console.log(' ✅ ' + devId + ' 同步成功');
return true;
} catch (e) {
console.error(' ⚠️ ' + devId + ' 同步失败: ' + e.message);
writeSyslog({
time: new Date().toISOString(),
type: 'notion_sync_error',
dev_id: devId,
error: e.message
});
return false;
}
}
// ━━━ 主逻辑 ━━━
async function main() {
const args = parseArgs();
console.log('📡 开发者门户 · Notion 动态同步');
console.log(' 开发者: ' + (args.devIds.join(', ') || '无'));
console.log(' 操作: ' + args.action);
console.log(' 时间: ' + args.timestamp);
console.log('');
if (args.devIds.length === 0) {
console.log('⚠️ 无开发者编号,跳过同步');
process.exit(0);
}
if (!NOTION_API_KEY || !DEV_PROFILE_DB_ID) {
console.log('⚠️ Notion 配置缺失 (NOTION_API_KEY / DEV_PROFILE_DB_ID)');
console.log(' 动态将写入本地 SYSLOG');
writeSyslog({
time: args.timestamp,
type: 'channel_deploy',
dev_ids: args.devIds,
action: args.action,
note: 'Notion未配置写入本地'
});
process.exit(0);
}
let success = 0;
for (const devId of args.devIds) {
const ok = await syncDevActivity(devId, args.action, args.timestamp);
if (ok) success++;
}
console.log('');
console.log('同步结果: ' + success + '/' + args.devIds.length + ' 成功');
}
main().catch(function(e) {
console.error('❌ 同步异常: ' + e.message);
writeSyslog({ time: new Date().toISOString(), type: 'sync_crash', error: e.message });
// Non-fatal — don't block the deploy
process.exit(0);
});