🎉 first commit

This commit is contained in:
LIlGG
2025-09-24 13:06:25 +08:00
commit 1f4fb103e9
409 changed files with 61222 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
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 失败');
}
}