497 lines
16 KiB
JavaScript
497 lines
16 KiB
JavaScript
|
|
/**
|
|||
|
|
* ═══════════════════════════════════════════════════════════
|
|||
|
|
* 模块F · Notion权限自动修复Agent MCP 工具
|
|||
|
|
* ═══════════════════════════════════════════════════════════
|
|||
|
|
*
|
|||
|
|
* 签发: 铸渊 · ICE-GL-ZY001
|
|||
|
|
* 版权: 国作登字-2026-A-00037559
|
|||
|
|
*
|
|||
|
|
* 自动检测和恢复Notion代理Agent的权限
|
|||
|
|
* 减少冰朔手动操作
|
|||
|
|
*
|
|||
|
|
* 工具清单:
|
|||
|
|
* notionCheckPermissions — 检查Notion权限状态
|
|||
|
|
* notionRepairPermissions — 尝试修复权限
|
|||
|
|
* notionListSharedPages — 列出已共享的页面/数据库
|
|||
|
|
* notionGenerateRepairGuide — 生成权限修复指南(给冰朔)
|
|||
|
|
* notionPermissionReport — 生成权限状态报告
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
const cos = require('../cos');
|
|||
|
|
|
|||
|
|
// 运行时可选加载 notion-client
|
|||
|
|
let notionClient = null;
|
|||
|
|
try {
|
|||
|
|
notionClient = require('../notion-client');
|
|||
|
|
} catch {
|
|||
|
|
// Notion模块未安装时降级
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── 已知数据库别名 ───
|
|||
|
|
const KNOWN_DATABASES = {
|
|||
|
|
changelog: { env: 'ZY_NOTION_CHANGELOG_DB', name: '变更日志' },
|
|||
|
|
receipt: { env: 'ZY_NOTION_RECEIPT_DB', name: '回执' },
|
|||
|
|
syslog: { env: 'ZY_NOTION_SYSLOG_DB', name: '系统日志' }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* notionCheckPermissions — 检查Notion权限状态
|
|||
|
|
*
|
|||
|
|
* 检测Notion API连接、数据库访问权限、页面读写权限
|
|||
|
|
*
|
|||
|
|
* input:
|
|||
|
|
* check_databases: boolean — 是否检查数据库权限(默认true)
|
|||
|
|
* check_pages: boolean — 是否检查页面权限(默认true)
|
|||
|
|
* database_ids: string[] — 额外检查的数据库ID列表(可选)
|
|||
|
|
*/
|
|||
|
|
async function notionCheckPermissions(input) {
|
|||
|
|
const { check_databases, check_pages, database_ids } = input || {};
|
|||
|
|
const shouldCheckDatabases = check_databases !== false;
|
|||
|
|
const shouldCheckPages = check_pages !== false;
|
|||
|
|
|
|||
|
|
const report = {
|
|||
|
|
timestamp: new Date().toISOString(),
|
|||
|
|
api_connection: { status: 'unknown' },
|
|||
|
|
databases: {},
|
|||
|
|
pages: {},
|
|||
|
|
issues: [],
|
|||
|
|
recommendations: []
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 1. 检查API连接
|
|||
|
|
if (!notionClient) {
|
|||
|
|
report.api_connection = {
|
|||
|
|
status: 'unavailable',
|
|||
|
|
reason: 'Notion客户端未加载(ZY_NOTION_TOKEN可能未配置)'
|
|||
|
|
};
|
|||
|
|
report.issues.push({
|
|||
|
|
type: 'api_unavailable',
|
|||
|
|
severity: 'critical',
|
|||
|
|
message: 'Notion API不可用',
|
|||
|
|
fix: '请确认ZY_NOTION_TOKEN环境变量已正确配置'
|
|||
|
|
});
|
|||
|
|
return report;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const connectionCheck = await notionClient.checkConnection();
|
|||
|
|
report.api_connection = {
|
|||
|
|
status: connectionCheck.connected ? 'connected' : 'disconnected',
|
|||
|
|
user: connectionCheck.user || null,
|
|||
|
|
type: connectionCheck.type || null,
|
|||
|
|
error: connectionCheck.error || null
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (!connectionCheck.connected) {
|
|||
|
|
report.issues.push({
|
|||
|
|
type: 'api_disconnected',
|
|||
|
|
severity: 'critical',
|
|||
|
|
message: `Notion API连接失败: ${connectionCheck.error || connectionCheck.reason}`,
|
|||
|
|
fix: '请检查ZY_NOTION_TOKEN是否过期或失效'
|
|||
|
|
});
|
|||
|
|
return report;
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
report.api_connection = { status: 'error', error: err.message };
|
|||
|
|
report.issues.push({
|
|||
|
|
type: 'api_error',
|
|||
|
|
severity: 'critical',
|
|||
|
|
message: `Notion API异常: ${err.message}`,
|
|||
|
|
fix: '请检查网络连接和Token配置'
|
|||
|
|
});
|
|||
|
|
return report;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 检查数据库权限
|
|||
|
|
if (shouldCheckDatabases) {
|
|||
|
|
for (const [alias, config] of Object.entries(KNOWN_DATABASES)) {
|
|||
|
|
const dbId = process.env[config.env];
|
|||
|
|
if (!dbId) {
|
|||
|
|
report.databases[alias] = {
|
|||
|
|
status: 'not_configured',
|
|||
|
|
env: config.env,
|
|||
|
|
name: config.name
|
|||
|
|
};
|
|||
|
|
report.issues.push({
|
|||
|
|
type: 'db_not_configured',
|
|||
|
|
severity: 'warning',
|
|||
|
|
database: alias,
|
|||
|
|
message: `数据库${config.name}未配置: ${config.env}环境变量缺失`,
|
|||
|
|
fix: `请配置${config.env}环境变量`
|
|||
|
|
});
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const schema = await notionClient.getDatabaseSchema(dbId);
|
|||
|
|
report.databases[alias] = {
|
|||
|
|
status: 'accessible',
|
|||
|
|
id: dbId,
|
|||
|
|
name: config.name,
|
|||
|
|
title: schema.title,
|
|||
|
|
properties: Object.keys(schema.properties).length
|
|||
|
|
};
|
|||
|
|
} catch (err) {
|
|||
|
|
report.databases[alias] = {
|
|||
|
|
status: 'no_access',
|
|||
|
|
id: dbId,
|
|||
|
|
name: config.name,
|
|||
|
|
error: err.message
|
|||
|
|
};
|
|||
|
|
report.issues.push({
|
|||
|
|
type: 'db_no_access',
|
|||
|
|
severity: 'high',
|
|||
|
|
database: alias,
|
|||
|
|
message: `无法访问数据库${config.name}: ${err.message}`,
|
|||
|
|
fix: `请在Notion中将数据库${config.name}共享给Integration`
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查额外的数据库
|
|||
|
|
if (database_ids) {
|
|||
|
|
for (const dbId of database_ids) {
|
|||
|
|
try {
|
|||
|
|
const schema = await notionClient.getDatabaseSchema(dbId);
|
|||
|
|
report.databases[dbId] = {
|
|||
|
|
status: 'accessible',
|
|||
|
|
title: schema.title,
|
|||
|
|
properties: Object.keys(schema.properties).length
|
|||
|
|
};
|
|||
|
|
} catch (err) {
|
|||
|
|
report.databases[dbId] = {
|
|||
|
|
status: 'no_access',
|
|||
|
|
error: err.message
|
|||
|
|
};
|
|||
|
|
report.issues.push({
|
|||
|
|
type: 'db_no_access',
|
|||
|
|
severity: 'warning',
|
|||
|
|
database: dbId,
|
|||
|
|
message: `无法访问数据库 ${dbId}: ${err.message}`
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 检查页面权限
|
|||
|
|
if (shouldCheckPages) {
|
|||
|
|
const bulletinPageId = process.env.ZY_NOTION_BULLETIN_PAGE;
|
|||
|
|
if (bulletinPageId) {
|
|||
|
|
try {
|
|||
|
|
await notionClient.readPage(bulletinPageId);
|
|||
|
|
report.pages.bulletin = { status: 'accessible', id: bulletinPageId };
|
|||
|
|
} catch (err) {
|
|||
|
|
report.pages.bulletin = {
|
|||
|
|
status: 'no_access',
|
|||
|
|
id: bulletinPageId,
|
|||
|
|
error: err.message
|
|||
|
|
};
|
|||
|
|
report.issues.push({
|
|||
|
|
type: 'page_no_access',
|
|||
|
|
severity: 'warning',
|
|||
|
|
page: 'bulletin',
|
|||
|
|
message: `无法访问公告板页面: ${err.message}`,
|
|||
|
|
fix: '请在Notion中将该页面共享给Integration'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. 生成建议
|
|||
|
|
if (report.issues.length === 0) {
|
|||
|
|
report.recommendations.push('✅ 所有权限正常,无需操作');
|
|||
|
|
} else {
|
|||
|
|
const criticals = report.issues.filter(i => i.severity === 'critical');
|
|||
|
|
const highs = report.issues.filter(i => i.severity === 'high');
|
|||
|
|
|
|||
|
|
if (criticals.length > 0) {
|
|||
|
|
report.recommendations.push('🔴 存在严重权限问题,需要立即处理');
|
|||
|
|
}
|
|||
|
|
if (highs.length > 0) {
|
|||
|
|
report.recommendations.push('🟡 存在数据库访问权限问题,请手动共享');
|
|||
|
|
}
|
|||
|
|
report.recommendations.push('💡 执行 notionGenerateRepairGuide 获取详细修复步骤');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return report;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* notionRepairPermissions — 尝试修复权限
|
|||
|
|
*
|
|||
|
|
* 注意:Notion API对权限管理的支持有限
|
|||
|
|
* 只能尝试验证和报告,大部分需要人工在Notion界面操作
|
|||
|
|
*
|
|||
|
|
* input:
|
|||
|
|
* auto_retry: boolean — 是否自动重试连接
|
|||
|
|
*/
|
|||
|
|
async function notionRepairPermissions(input) {
|
|||
|
|
const { auto_retry } = input || {};
|
|||
|
|
const repairLog = [];
|
|||
|
|
let repaired = 0;
|
|||
|
|
let failed = 0;
|
|||
|
|
|
|||
|
|
// 1. 尝试重新连接
|
|||
|
|
if (auto_retry) {
|
|||
|
|
repairLog.push({ step: '重新初始化Notion客户端', status: 'attempting' });
|
|||
|
|
try {
|
|||
|
|
const check = await notionClient.checkConnection();
|
|||
|
|
if (check.connected) {
|
|||
|
|
repairLog.push({ step: '重新连接', status: 'success', user: check.user });
|
|||
|
|
repaired++;
|
|||
|
|
} else {
|
|||
|
|
repairLog.push({ step: '重新连接', status: 'failed', reason: check.reason });
|
|||
|
|
failed++;
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
repairLog.push({ step: '重新连接', status: 'failed', error: err.message });
|
|||
|
|
failed++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 验证各数据库的写入权限
|
|||
|
|
for (const [alias, config] of Object.entries(KNOWN_DATABASES)) {
|
|||
|
|
const dbId = process.env[config.env];
|
|||
|
|
if (!dbId) continue;
|
|||
|
|
|
|||
|
|
repairLog.push({ step: `验证${config.name}写入权限`, status: 'checking' });
|
|||
|
|
try {
|
|||
|
|
// 尝试查询数据库(只读测试)
|
|||
|
|
await notionClient.queryDatabase(dbId, null, null, 1);
|
|||
|
|
repairLog.push({ step: `验证${config.name}`, status: 'success', permission: 'read' });
|
|||
|
|
repaired++;
|
|||
|
|
} catch (err) {
|
|||
|
|
repairLog.push({
|
|||
|
|
step: `验证${config.name}`,
|
|||
|
|
status: 'failed',
|
|||
|
|
error: err.message,
|
|||
|
|
manual_fix: `请在Notion中: 打开数据库 → 右上角"..." → "连接" → 添加Integration`
|
|||
|
|
});
|
|||
|
|
failed++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
status: failed === 0 ? 'all_repaired' : (repaired > 0 ? 'partial' : 'all_failed'),
|
|||
|
|
repaired,
|
|||
|
|
failed,
|
|||
|
|
log: repairLog,
|
|||
|
|
note: failed > 0
|
|||
|
|
? '部分权限需要人工在Notion界面修复。请执行 notionGenerateRepairGuide 获取详细步骤。'
|
|||
|
|
: '所有可自动修复的权限已恢复。'
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* notionListSharedPages — 列出已共享的页面/数据库
|
|||
|
|
*
|
|||
|
|
* input:
|
|||
|
|
* limit: number — 最大数量(默认20)
|
|||
|
|
*/
|
|||
|
|
async function notionListSharedPages(input) {
|
|||
|
|
const { limit } = input || {};
|
|||
|
|
if (!notionClient) throw new Error('Notion客户端未加载');
|
|||
|
|
|
|||
|
|
// Notion API的search接口可以列出所有已共享的页面
|
|||
|
|
const { Client } = require('@notionhq/client');
|
|||
|
|
const client = new Client({ auth: process.env.ZY_NOTION_TOKEN });
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const response = await client.search({
|
|||
|
|
page_size: Math.min(limit || 20, 100)
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const items = response.results.map(item => ({
|
|||
|
|
id: item.id,
|
|||
|
|
type: item.object, // 'page' or 'database'
|
|||
|
|
title: item.object === 'database'
|
|||
|
|
? (item.title?.map(t => t.plain_text).join('') || '未命名数据库')
|
|||
|
|
: extractPageTitle(item),
|
|||
|
|
created_time: item.created_time,
|
|||
|
|
last_edited_time: item.last_edited_time,
|
|||
|
|
url: item.url || null,
|
|||
|
|
archived: item.archived || false
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
items,
|
|||
|
|
total: items.length,
|
|||
|
|
has_more: response.has_more,
|
|||
|
|
databases: items.filter(i => i.type === 'database').length,
|
|||
|
|
pages: items.filter(i => i.type === 'page').length
|
|||
|
|
};
|
|||
|
|
} catch (err) {
|
|||
|
|
throw new Error(`搜索失败: ${err.message}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* notionGenerateRepairGuide — 生成权限修复指南(给冰朔)
|
|||
|
|
*
|
|||
|
|
* 基于当前权限检查结果,生成一份详细的操作指南
|
|||
|
|
*
|
|||
|
|
* input:
|
|||
|
|
* write_to_cos: boolean — 是否同时写入COS桶(供冰朔远程查看)
|
|||
|
|
* bucket: string — 目标COS桶
|
|||
|
|
*/
|
|||
|
|
async function notionGenerateRepairGuide(input) {
|
|||
|
|
const { write_to_cos, bucket } = input || {};
|
|||
|
|
|
|||
|
|
// 先执行权限检查
|
|||
|
|
const checkResult = await notionCheckPermissions({});
|
|||
|
|
const issues = checkResult.issues;
|
|||
|
|
|
|||
|
|
if (issues.length === 0) {
|
|||
|
|
return {
|
|||
|
|
status: 'no_issues',
|
|||
|
|
guide: '✅ 当前所有Notion权限正常,无需修复。'
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成修复指南
|
|||
|
|
const guide = [];
|
|||
|
|
guide.push('# Notion权限修复指南');
|
|||
|
|
guide.push(`\n生成时间: ${new Date().toISOString()}`);
|
|||
|
|
guide.push(`发现 ${issues.length} 个问题需要修复:\n`);
|
|||
|
|
|
|||
|
|
for (let i = 0; i < issues.length; i++) {
|
|||
|
|
const issue = issues[i];
|
|||
|
|
guide.push(`## 问题 ${i + 1}: ${issue.message}`);
|
|||
|
|
guide.push(`- 严重程度: ${issue.severity}`);
|
|||
|
|
guide.push(`- 类型: ${issue.type}`);
|
|||
|
|
|
|||
|
|
if (issue.fix) {
|
|||
|
|
guide.push(`- 修复方法: ${issue.fix}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 提供详细操作步骤
|
|||
|
|
switch (issue.type) {
|
|||
|
|
case 'api_unavailable':
|
|||
|
|
guide.push('\n### 操作步骤:');
|
|||
|
|
guide.push('1. 登录 https://www.notion.so/my-integrations');
|
|||
|
|
guide.push('2. 找到"光湖系统"Integration');
|
|||
|
|
guide.push('3. 复制"Internal Integration Secret"');
|
|||
|
|
guide.push('4. 在服务器上执行: `export ZY_NOTION_TOKEN=你的secret`');
|
|||
|
|
guide.push('5. 重启MCP Server: `pm2 restart age-os-mcp`');
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'api_disconnected':
|
|||
|
|
guide.push('\n### 操作步骤:');
|
|||
|
|
guide.push('1. 访问 https://www.notion.so/my-integrations');
|
|||
|
|
guide.push('2. 检查Integration是否被禁用');
|
|||
|
|
guide.push('3. 如果Token过期,重新生成并更新环境变量');
|
|||
|
|
guide.push('4. 重启MCP Server');
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'db_not_configured':
|
|||
|
|
guide.push(`\n### 操作步骤:`);
|
|||
|
|
guide.push(`1. 在Notion中找到"${issue.database}"数据库`);
|
|||
|
|
guide.push('2. 复制数据库URL中的ID(32位十六进制字符串)');
|
|||
|
|
guide.push(`3. 在服务器上配置: export ${KNOWN_DATABASES[issue.database]?.env || 'ZY_NOTION_xxx_DB'}=数据库ID`);
|
|||
|
|
guide.push('4. 重启MCP Server');
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'db_no_access':
|
|||
|
|
guide.push('\n### 操作步骤:');
|
|||
|
|
guide.push('1. 在Notion中打开对应数据库');
|
|||
|
|
guide.push('2. 点击右上角 "..." 菜单');
|
|||
|
|
guide.push('3. 选择 "连接" → "添加连接"');
|
|||
|
|
guide.push('4. 搜索并选择"光湖系统"Integration');
|
|||
|
|
guide.push('5. 确认授权');
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'page_no_access':
|
|||
|
|
guide.push('\n### 操作步骤:');
|
|||
|
|
guide.push('1. 在Notion中打开对应页面');
|
|||
|
|
guide.push('2. 点击右上角 "分享" 按钮');
|
|||
|
|
guide.push('3. 选择 "邀请" → 找到"光湖系统"Integration');
|
|||
|
|
guide.push('4. 确认共享');
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
guide.push('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
guide.push('---');
|
|||
|
|
guide.push('*此指南由铸渊自动生成 · 如有疑问请唤醒铸渊*');
|
|||
|
|
|
|||
|
|
const guideText = guide.join('\n');
|
|||
|
|
|
|||
|
|
// 写入COS桶
|
|||
|
|
if (write_to_cos) {
|
|||
|
|
const targetBucket = bucket || 'team';
|
|||
|
|
const key = `zhuyuan/directives/notion-repair-guide-${formatDate()}.md`;
|
|||
|
|
await cos.write(targetBucket, key, guideText, 'text/markdown');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
status: 'guide_generated',
|
|||
|
|
issues_count: issues.length,
|
|||
|
|
guide: guideText,
|
|||
|
|
cos_key: write_to_cos ? `zhuyuan/directives/notion-repair-guide-${formatDate()}.md` : null
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* notionPermissionReport — 生成权限状态报告
|
|||
|
|
*
|
|||
|
|
* input:
|
|||
|
|
* write_to_cos: boolean — 是否写入COS桶
|
|||
|
|
* bucket: string — 目标COS桶
|
|||
|
|
*/
|
|||
|
|
async function notionPermissionReport(input) {
|
|||
|
|
const { write_to_cos, bucket } = input || {};
|
|||
|
|
|
|||
|
|
const checkResult = await notionCheckPermissions({});
|
|||
|
|
|
|||
|
|
const report = {
|
|||
|
|
report_type: 'notion_permission_check',
|
|||
|
|
timestamp: new Date().toISOString(),
|
|||
|
|
api_status: checkResult.api_connection.status,
|
|||
|
|
databases_checked: Object.keys(checkResult.databases).length,
|
|||
|
|
databases_accessible: Object.values(checkResult.databases).filter(d => d.status === 'accessible').length,
|
|||
|
|
issues: checkResult.issues,
|
|||
|
|
issues_count: checkResult.issues.length,
|
|||
|
|
critical_count: checkResult.issues.filter(i => i.severity === 'critical').length,
|
|||
|
|
recommendations: checkResult.recommendations,
|
|||
|
|
overall: checkResult.issues.length === 0 ? 'healthy' : 'needs_attention'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (write_to_cos) {
|
|||
|
|
const targetBucket = bucket || 'team';
|
|||
|
|
const key = `zhuyuan/reports/notion-permission-${formatDate()}.json`;
|
|||
|
|
await cos.write(targetBucket, key, JSON.stringify(report, null, 2), 'application/json');
|
|||
|
|
report.cos_key = key;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return report;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── 辅助函数 ───
|
|||
|
|
|
|||
|
|
function extractPageTitle(page) {
|
|||
|
|
if (!page.properties) return '未命名';
|
|||
|
|
for (const [, prop] of Object.entries(page.properties)) {
|
|||
|
|
if (prop.type === 'title' && prop.title) {
|
|||
|
|
return prop.title.map(t => t.plain_text || '').join('') || '未命名';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return '未命名';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function formatDate() {
|
|||
|
|
const d = new Date();
|
|||
|
|
return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = {
|
|||
|
|
notionCheckPermissions,
|
|||
|
|
notionRepairPermissions,
|
|||
|
|
notionListSharedPages,
|
|||
|
|
notionGenerateRepairGuide,
|
|||
|
|
notionPermissionReport
|
|||
|
|
};
|