- Replaced nodemon with tsx for development script in package.json - Updated devDependencies to include tsx and removed nodemon - Reformatted code in worker.ts for better readability - Improved handling of API calls and logging in worker.ts
154 lines
4.0 KiB
TypeScript
154 lines
4.0 KiB
TypeScript
import { Job, Worker } from "bullmq";
|
|
import dotenv from "dotenv";
|
|
import { redisConfig } from "./redis.js";
|
|
|
|
dotenv.config();
|
|
|
|
console.log("👷 Gitea Deploy Worker Started...");
|
|
|
|
export interface DeployJobData {
|
|
repoName: string;
|
|
chatId: string;
|
|
files: Record<string, string>;
|
|
}
|
|
|
|
// Configuration
|
|
const GITEA_API_URL =
|
|
process.env.GITEA_API_URL || "https://git.mworld.cloud/api/v1";
|
|
const GITEA_TOKEN = process.env.GITEA_TOKEN;
|
|
const GITEA_USERNAME = process.env.GITEA_USERNAME;
|
|
|
|
if (!GITEA_TOKEN || !GITEA_USERNAME) {
|
|
throw new Error("Missing GITEA_TOKEN or GITEA_USERNAME");
|
|
}
|
|
|
|
// Helper to call Gitea API
|
|
async function callGitea(method: string, path: string, body?: any) {
|
|
const headers: Record<string, string> = {
|
|
Authorization: `token ${GITEA_TOKEN}`,
|
|
"Content-Type": "application/json",
|
|
};
|
|
|
|
const response = await fetch(`${GITEA_API_URL}${path}`, {
|
|
method,
|
|
headers,
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
|
|
if (!response.ok && response.status !== 404) {
|
|
const errorText = await response.text();
|
|
throw new Error(`Gitea API Error ${response.status}: ${errorText}`);
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
const worker = new Worker<DeployJobData>(
|
|
"github-deployments",
|
|
async (job: Job) => {
|
|
const { repoName, chatId, files } = job.data;
|
|
|
|
// 1. Sanitize Repo Name
|
|
const sanitizedRepoName = repoName
|
|
.toLowerCase()
|
|
.trim()
|
|
.replace(/\s+/g, "-")
|
|
.replace(/[^\w-]/g, "");
|
|
|
|
console.log(
|
|
`[Job ${job.id}] Processing deployment for ${sanitizedRepoName} (Chat: ${chatId})...`
|
|
);
|
|
|
|
try {
|
|
await job.updateProgress(10);
|
|
|
|
let repoDetails: any;
|
|
|
|
const checkRepo = await callGitea(
|
|
"GET",
|
|
`/repos/${GITEA_USERNAME}/${sanitizedRepoName}`
|
|
);
|
|
|
|
if (checkRepo.status === 404) {
|
|
console.log(
|
|
`[Job ${job.id}] Repo not found, creating new repo: ${sanitizedRepoName}`
|
|
);
|
|
|
|
// Create Repo
|
|
const createRes = await callGitea("POST", "/user/repos", {
|
|
name: sanitizedRepoName,
|
|
private: true,
|
|
auto_init: true,
|
|
description: `Generated by MWorld for chat ${chatId}`,
|
|
});
|
|
|
|
if (!createRes.ok) throw new Error("Failed to create repository");
|
|
|
|
repoDetails = await createRes.json();
|
|
|
|
await new Promise((r) => setTimeout(r, 2000));
|
|
} else {
|
|
repoDetails = await checkRepo.json();
|
|
}
|
|
|
|
const defaultBranch = repoDetails.default_branch || "main"; //
|
|
|
|
await job.updateProgress(30);
|
|
|
|
// 3. Prepare Batch File Operations
|
|
const fileOperations = Object.entries(files).map(([path, content]) => ({
|
|
operation: "upload",
|
|
path: path,
|
|
content: Buffer.from(content as string).toString("base64"),
|
|
}));
|
|
|
|
await job.updateProgress(60);
|
|
|
|
// 4. Commit and Push (Atomic Batch Operation)
|
|
const commitRes = await callGitea(
|
|
"POST",
|
|
`/repos/${GITEA_USERNAME}/${sanitizedRepoName}/contents`,
|
|
{
|
|
branch: defaultBranch,
|
|
message: `Deploy updates from MWorld ⚡️ (Chat ${chatId})`,
|
|
files: fileOperations,
|
|
author: {
|
|
name: "MWorld Bot",
|
|
email: "bot@mworld.com",
|
|
},
|
|
}
|
|
);
|
|
|
|
if (!commitRes.ok) {
|
|
throw new Error(`Failed to commit files: ${await commitRes.text()}`);
|
|
}
|
|
|
|
await job.updateProgress(100);
|
|
|
|
const repoUrl = `${GITEA_API_URL.replace(
|
|
"/api/v1",
|
|
""
|
|
)}/${GITEA_USERNAME}/${sanitizedRepoName}`;
|
|
console.log(`[Job ${job.id}] Deployment Complete! 🚀 URL: ${repoUrl}`);
|
|
|
|
return { success: true, url: repoUrl };
|
|
} catch (error: any) {
|
|
console.error(`[Job ${job.id}] Failed:`, error.message);
|
|
throw error;
|
|
}
|
|
},
|
|
{
|
|
connection: redisConfig,
|
|
concurrency: 5,
|
|
limiter: {
|
|
max: 10,
|
|
duration: 1000,
|
|
},
|
|
}
|
|
);
|
|
|
|
// Listener for errors
|
|
worker.on("failed", (job, err) => {
|
|
console.error(`[Job ${job?.id}] Failed with error ${err.message}`);
|
|
});
|