init:taskbot 1.1.0

This commit is contained in:
2026-01-29 15:38:49 +08:00
commit 4dcb117601
25 changed files with 6070 additions and 0 deletions

56
src/config/env.js Normal file
View File

@@ -0,0 +1,56 @@
require('dotenv').config();
const config = {
app: {
port: process.env.PORT || 3000,
dbPath: process.env.DB_FILE_PATH || './sync.sqlite',
rate : process.env.RATE_LIMIT_WINDOW || 10000,
maxRequests: process.env.MAX_REQUESTS_PER_WINDOW || 20,
debugMode: process.env.DEBUG_MODE === 'true',
logRetentionDays: process.env.LOG_RETENTION_DAYS || 30
},
gitea: {
baseUrl: process.env.GITEA_BASE_URL,
token: process.env.GITEA_TOKEN,
secret: process.env.GITEA_WEBHOOK_SECRET,
botId: parseInt(process.env.GITEA_BOT_ID || '0'),
botName: process.env.GITEA_BOT_NAME
},
jira: {
baseUrl: process.env.JIRA_BASE_URL,
botId: process.env.JIRA_BOT_ID || '',
botName: process.env.JIRA_BOT_NAME
}
};
const requiredVars = [
{ key: 'GITEA_BASE_URL', value: config.gitea.baseUrl },
{ key: 'GITEA_TOKEN', value: config.gitea.token },
{ key: 'GITEA_WEBHOOK_SECRET', value: config.gitea.secret },
{ key: 'JIRA_BASE_URL', value: config.jira.baseUrl }
];
const missingVars = requiredVars.filter(v => !v.value).map(v => v.key);
if (missingVars.length > 0) {
console.error(`[ERROR] Missing required environment variables: ${missingVars.join(', ')}`);
console.error('[ERROR] Please configure these in your .env file');
process.exit(1);
}
if (process.env.JIRA_PAT) {
//个人访问令牌鉴权
config.jira.authHeader = {
'Authorization': `Bearer ${process.env.JIRA_PAT}`
};
} else if (process.env.JIRA_USERNAME && process.env.JIRA_PASSWORD) {
//账号密码鉴权
const authString = Buffer.from(`${process.env.JIRA_USERNAME}:${process.env.JIRA_PASSWORD}`).toString('base64');
config.jira.authHeader = {
'Authorization': `Basic ${authString}`
};
} else {
console.error("[ERROR] Missing JIRA authentication: Please configure JIRA_PAT or JIRA_USERNAME/PASSWORD");
process.exit(1);
}
module.exports = config;

144
src/config/mappings.js Normal file
View File

@@ -0,0 +1,144 @@
const fs = require('fs');
const path = require('path');
// 读取配置文件路径
const configPath = path.join(__dirname, '../../mappings.json');
let mappingsConfig = null;
/**
* 加载映射配置文件
* @returns {Object} 配置对象
*/
function loadMappings() {
if (mappingsConfig) {
return mappingsConfig;
}
try {
const configContent = fs.readFileSync(configPath, 'utf8');
mappingsConfig = JSON.parse(configContent);
// 处理环境变量替换
processEnvVariables(mappingsConfig);
return mappingsConfig;
} catch (error) {
throw new Error(`无法加载映射配置文件 ${configPath}: ${error.message}`);
}
}
/**
* 递归处理配置中的环境变量
* @param {Object} obj 配置对象
*/
function processEnvVariables(obj) {
for (const key in obj) {
if (typeof obj[key] === 'string' && obj[key].startsWith('${') && obj[key].endsWith('}')) {
const envVar = obj[key].slice(2, -1);
obj[key] = process.env[envVar] || obj[key];
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
processEnvVariables(obj[key]);
}
}
}
/**
* 获取所有仓库配置
* @returns {Object} 仓库配置对象
*/
const repositories = new Proxy({}, {
get(target, prop) {
const config = loadMappings();
return config.repositories[prop];
},
ownKeys() {
const config = loadMappings();
return Object.keys(config.repositories);
},
getOwnPropertyDescriptor(target, prop) {
const config = loadMappings();
if (prop in config.repositories) {
return {
enumerable: true,
configurable: true
};
}
}
});
/**
* 获取默认映射配置
* @returns {Object} 默认映射配置
*/
const defaultMappings = new Proxy({}, {
get(target, prop) {
const config = loadMappings();
return config.defaultMappings[prop];
}
});
/**
* 获取仓库配置
* @param {string} repoFullName 仓库完整名称 (owner/repo)
* @returns {Object|null} 仓库配置对象,如果不存在返回 null
*/
function getRepoConfig(repoFullName) {
const config = loadMappings();
const repoConfig = config.repositories[repoFullName];
if (!repoConfig) {
return null;
}
return {
jira: repoConfig.jira,
priorities: repoConfig.priorities || config.defaultMappings.priorities,
types: repoConfig.types || config.defaultMappings.types,
sprints: repoConfig.sprints || config.defaultMappings.sprints,
transitions: repoConfig.transitions || config.defaultMappings.transitions
};
}
/**
* 获取所有已配置的仓库列表
* @returns {string[]} 仓库名称数组
*/
function getConfiguredRepos() {
const config = loadMappings();
return Object.keys(config.repositories);
}
/**
* 根据Jira项目Key反查对应的Gitea仓库
* 返回第一个匹配的仓库配置如果一个Jira项目对应多个Gitea仓库只返回第一个
* @param {string} jiraProjectKey Jira项目Key
* @returns {Object|null} 包含仓库Key和配置的对象如果不存在返回 null
*/
function getRepoByJiraProject(jiraProjectKey) {
const config = loadMappings();
for (const [repoKey, repoConfig] of Object.entries(config.repositories)) {
if (repoConfig.jira && repoConfig.jira.projectKey === jiraProjectKey) {
return {
repoKey,
config: {
jira: repoConfig.jira,
priorities: repoConfig.priorities || config.defaultMappings.priorities,
types: repoConfig.types || config.defaultMappings.types,
sprints: repoConfig.sprints || config.defaultMappings.sprints,
transitions: repoConfig.transitions || config.defaultMappings.transitions
}
};
}
}
return null;
}
module.exports = {
repositories,
defaultMappings,
getRepoConfig,
getConfiguredRepos,
getRepoByJiraProject
};