218 lines
7.1 KiB
TypeScript
218 lines
7.1 KiB
TypeScript
import { type ActionFunctionArgs } from '@remix-run/node';
|
||
import { get1PanelConnectionSettings, save1PanelConnectionSettings } from '~/lib/.server/connectionSettings';
|
||
import { createOrUpdateDeployment, getLatestDeployment } from '~/lib/.server/deployment';
|
||
import { createScopedLogger } from '~/lib/.server/logger';
|
||
import {
|
||
createWebsite,
|
||
getWebsite,
|
||
getWebsiteByPrimaryDomain,
|
||
type UploadFileContent,
|
||
uploadFiles,
|
||
} from '~/routes/api.1panel.$action/1panel.server';
|
||
import type { _1PanelWebsite, _1PanelWebsiteInfo } from '~/types/1panel';
|
||
import { DeploymentPlatformEnum, DeploymentStatusEnum } from '~/types/deployment';
|
||
import { errorResponse, successResponse } from '~/utils/api-response';
|
||
|
||
interface DeployRequestBody {
|
||
websiteId: number;
|
||
files: Record<string, string>;
|
||
chatId: string;
|
||
serverUrl?: string;
|
||
apiKey?: string;
|
||
websiteDomain?: string;
|
||
protocol?: string;
|
||
}
|
||
|
||
export type HandleDeployArgs = ActionFunctionArgs & {
|
||
userId: string;
|
||
};
|
||
|
||
const logger = createScopedLogger('api.1panel.deploy');
|
||
|
||
export async function handleDeploy({ request, userId }: HandleDeployArgs) {
|
||
try {
|
||
const {
|
||
websiteId,
|
||
files,
|
||
chatId,
|
||
serverUrl: requestServerUrl,
|
||
apiKey: requestApiKey,
|
||
websiteDomain,
|
||
protocol,
|
||
} = (await request.json()) as DeployRequestBody;
|
||
|
||
// 从用户设置中获取连接信息
|
||
let connectionSettings = await get1PanelConnectionSettings(userId);
|
||
|
||
// 如果请求体中提供了连接信息,优先使用请求体中的信息,并更新用户设置
|
||
if (requestServerUrl && requestApiKey) {
|
||
connectionSettings = {
|
||
serverUrl: requestServerUrl,
|
||
apiKey: requestApiKey,
|
||
};
|
||
|
||
// 更新用户设置
|
||
await save1PanelConnectionSettings(userId, requestServerUrl, requestApiKey);
|
||
}
|
||
|
||
// 如果没有连接信息,返回错误
|
||
if (!connectionSettings) {
|
||
logger.warn('未配置1Panel连接信息');
|
||
return errorResponse(401, '未配置1Panel连接信息,请先设置服务器地址和API密钥');
|
||
}
|
||
|
||
const { serverUrl, apiKey } = connectionSettings;
|
||
|
||
logger.debug('action => request', { websiteId, files, chatId, serverUrl, websiteDomain, protocol });
|
||
|
||
const existingDeployment = await getLatestDeployment(userId, chatId, DeploymentPlatformEnum._1PANEL);
|
||
let targetWebsiteId;
|
||
if (websiteId) {
|
||
targetWebsiteId = websiteId;
|
||
} else if (existingDeployment?.deploymentId) {
|
||
targetWebsiteId = parseInt(existingDeployment.deploymentId);
|
||
} else {
|
||
targetWebsiteId = undefined;
|
||
}
|
||
|
||
let websiteInfo: _1PanelWebsiteInfo | undefined;
|
||
if (targetWebsiteId) {
|
||
const websiteResponse = await getWebsite({
|
||
serverUrl,
|
||
apiKey,
|
||
siteId: targetWebsiteId,
|
||
});
|
||
|
||
logger.debug('action => getWebsite', JSON.stringify(websiteResponse));
|
||
|
||
if (websiteResponse.data) {
|
||
const existingWebsite = websiteResponse.data as _1PanelWebsite;
|
||
websiteInfo = {
|
||
id: existingWebsite.id,
|
||
domain: existingWebsite.primaryDomain,
|
||
url: `${existingWebsite.protocol.toLowerCase()}://${existingWebsite.primaryDomain}`,
|
||
alias: existingWebsite.alias,
|
||
sitePath: existingWebsite.sitePath,
|
||
chatId,
|
||
};
|
||
}
|
||
}
|
||
|
||
if (!websiteInfo) {
|
||
// If no websiteId provided, create a new website
|
||
const alias = `upage-${chatId}-${Date.now()}`;
|
||
const createWebsiteResponse = await createWebsite({
|
||
serverUrl,
|
||
apiKey,
|
||
alias,
|
||
primaryDomain: websiteDomain,
|
||
proxyProtocol: `${protocol || 'http'}://`,
|
||
isSSL: protocol === 'https',
|
||
});
|
||
|
||
logger.debug('action => createWebsite', JSON.stringify(createWebsiteResponse));
|
||
|
||
if (createWebsiteResponse.code !== 200) {
|
||
logger.warn('无法创建网站', JSON.stringify(createWebsiteResponse));
|
||
return errorResponse(400, `无法创建网站: ${createWebsiteResponse.data?.message || 'Unknown error'}`);
|
||
}
|
||
|
||
const { domain } = createWebsiteResponse.data as { domain: string };
|
||
|
||
const webSiteInfo = await getWebsiteByPrimaryDomain(serverUrl, apiKey, domain);
|
||
if (webSiteInfo.code !== 200) {
|
||
logger.warn('无法获取网站信息', JSON.stringify(webSiteInfo));
|
||
return errorResponse(400, '无法获取网站信息');
|
||
}
|
||
if (webSiteInfo.data.items == null) {
|
||
logger.warn('获取网站失败,请检查 1Panel 日志', JSON.stringify(webSiteInfo));
|
||
return errorResponse(400, '获取网站失败,请检查 1Panel 日志');
|
||
}
|
||
|
||
const newWebsite = webSiteInfo.data.items.find((item) => item.alias === alias);
|
||
if (!newWebsite) {
|
||
logger.warn('无法获取网站信息', JSON.stringify(newWebsite));
|
||
return errorResponse(400, '无法获取网站信息');
|
||
}
|
||
|
||
targetWebsiteId = newWebsite.id;
|
||
websiteInfo = {
|
||
id: newWebsite.id,
|
||
domain: newWebsite.primaryDomain,
|
||
sitePath: newWebsite.sitePath,
|
||
alias: newWebsite.alias,
|
||
url: `${newWebsite.protocol.toLowerCase()}://${newWebsite.primaryDomain}`,
|
||
chatId,
|
||
};
|
||
}
|
||
|
||
logger.info('创建网站成功 => ', websiteInfo.id, websiteInfo.domain, websiteInfo.url);
|
||
|
||
if (!websiteInfo) {
|
||
return errorResponse(400, '无法创建网站');
|
||
}
|
||
|
||
const deploymentFiles: UploadFileContent[] = [];
|
||
for (const [filePath, content] of Object.entries(files)) {
|
||
const lastSlashIndex = filePath.lastIndexOf('/');
|
||
const folderPath = lastSlashIndex !== -1 ? filePath.substring(0, lastSlashIndex + 1) : '';
|
||
const fileName = lastSlashIndex !== -1 ? filePath.substring(lastSlashIndex + 1) : filePath;
|
||
// 获取 filename
|
||
deploymentFiles.push({
|
||
path: `${websiteInfo.sitePath}/index/${folderPath}`,
|
||
fileName,
|
||
data: content,
|
||
});
|
||
}
|
||
|
||
logger.debug('action => uploadFiles', JSON.stringify(deploymentFiles));
|
||
|
||
try {
|
||
await uploadFiles({
|
||
serverUrl,
|
||
apiKey,
|
||
version: 'v2',
|
||
files: deploymentFiles,
|
||
});
|
||
} catch (error) {
|
||
logger.warn('action => uploadFiles error', JSON.stringify(error));
|
||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||
return errorResponse(400, `无法上传文件: ${errorMessage}`);
|
||
}
|
||
|
||
try {
|
||
await createOrUpdateDeployment({
|
||
userId,
|
||
chatId,
|
||
platform: DeploymentPlatformEnum._1PANEL,
|
||
deploymentId: String(websiteInfo.id),
|
||
url: websiteInfo.url,
|
||
status: DeploymentStatusEnum.SUCCESS,
|
||
metadata: {
|
||
domain: websiteInfo.domain,
|
||
alias: websiteInfo.alias,
|
||
sitePath: websiteInfo.sitePath,
|
||
serverUrl,
|
||
},
|
||
});
|
||
logger.info(`为用户 ${userId} 创建了 1Panel 部署记录`);
|
||
} catch (error) {
|
||
logger.error('创建部署记录失败:', error);
|
||
}
|
||
|
||
return successResponse(
|
||
{
|
||
deploy: {
|
||
id: websiteInfo.id,
|
||
domain: websiteInfo.domain,
|
||
url: websiteInfo.url,
|
||
},
|
||
},
|
||
'部署成功',
|
||
);
|
||
} catch (error) {
|
||
console.error('1Panel deploy error:', error);
|
||
return errorResponse(500, '部署到 1Panel 失败');
|
||
}
|
||
}
|