From 7ccfe677b3752ab90043faf88388f6e94011cbef Mon Sep 17 00:00:00 2001 From: AmanDevelops Date: Sat, 10 Jan 2026 00:23:54 +0530 Subject: [PATCH] feat: update worker to use Gitea API for deployments and remove GitHub references --- .env.example | 12 +-- package-lock.json | 220 +--------------------------------------------- package.json | 5 +- worker.ts | 171 ++++++++++++++++------------------- 4 files changed, 90 insertions(+), 318 deletions(-) diff --git a/.env.example b/.env.example index 8f2861b..f4924a8 100644 --- a/.env.example +++ b/.env.example @@ -4,10 +4,12 @@ REDIS_URL=redis://username:your_password@HOST:PORT # ------------------------------ -# GitHub Configuration +# Gitea Configuration # ------------------------------ -# Scopes required: 'repo' (to create repos and push code) -GITHUB_TOKEN=ghp_xxxxxx +GITEA_API_URL=https://git.mworld.cloud/api/v1 -# The GitHub username associated with the token above -GITHUB_USERNAME=github_username \ No newline at end of file +# Scopes required: 'repo' (to create repos and push code) +GITEA_TOKEN=github_username + +# The Gitea username associated with the token above +GITEA_USERNAME=gitea_username \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1099fec..fe72eef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "name": "worker-for-website-generator", "version": "1.0.0", "dependencies": { - "@octokit/rest": "^21.0.0", "bullmq": "^5.66.4", "dotenv": "^16.4.5", "ioredis": "^5.4.1" @@ -145,191 +144,6 @@ "win32" ] }, - "node_modules/@octokit/auth-token": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", - "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.6.tgz", - "integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.2.2", - "@octokit/request": "^9.2.3", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "before-after-hook": "^3.0.2", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/endpoint": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", - "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", - "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", - "license": "MIT", - "dependencies": { - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", - "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz", - "integrity": "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.10.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" - } - }, - "node_modules/@octokit/plugin-request-log": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz", - "integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==", - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.5.0.tgz", - "integrity": "sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.10.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^24.2.0" - } - }, - "node_modules/@octokit/request": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", - "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^10.1.4", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "fast-content-type-parse": "^2.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/request-error": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", - "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz", - "integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==", - "license": "MIT", - "dependencies": { - "@octokit/core": "^6.1.4", - "@octokit/plugin-paginate-rest": "^11.4.2", - "@octokit/plugin-request-log": "^5.3.1", - "@octokit/plugin-rest-endpoint-methods": "^13.3.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^25.1.0" - } - }, "node_modules/@tsconfig/node10": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", @@ -423,12 +237,6 @@ "dev": true, "license": "MIT" }, - "node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "license": "Apache-2.0" - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -629,22 +437,6 @@ "url": "https://dotenvx.com" } }, - "node_modules/fast-content-type-parse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", - "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -704,9 +496,9 @@ "license": "ISC" }, "node_modules/ioredis": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.0.tgz", - "integrity": "sha512-T3VieIilNumOJCXI9SDgo4NnF6sZkd6XcmPi6qWtw4xqbt8nNz/ZVNiIH1L9puMTSHZh1mUWA4xKa2nWPF4NwQ==", + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.1.tgz", + "integrity": "sha512-BXNqFQ66oOsR82g9ajFFsR8ZKrjVvYCLyeML9IvSMAsP56XH2VXBdZjmI11p65nXXJxTEt1hie3J2QeFJVgrtQ==", "license": "MIT", "dependencies": { "@ioredis/commands": "1.5.0", @@ -1111,12 +903,6 @@ "dev": true, "license": "MIT" }, - "node_modules/universal-user-agent": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", - "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", - "license": "ISC" - }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", diff --git a/package.json b/package.json index ba1e5fc..fa014ea 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,10 @@ "type": "module", "scripts": { "build": "tsc", - "start": "node dist/worker.js", - "dev": "nodemon --exec ts-node worker.ts" + "start": "node --import=dotenv/config dist/worker.js", + "dev": "nodemon --exec 'ts-node --require dotenv/config' worker.ts" }, "dependencies": { - "@octokit/rest": "^21.0.0", "bullmq": "^5.66.4", "dotenv": "^16.4.5", "ioredis": "^5.4.1" diff --git a/worker.ts b/worker.ts index a73f622..7d919f7 100644 --- a/worker.ts +++ b/worker.ts @@ -1,11 +1,10 @@ -import { Octokit } from "@octokit/rest"; import { Job, Worker } from "bullmq"; import dotenv from "dotenv"; import { redisConfig } from "./redis.js"; dotenv.config(); -console.log("👷 Deploy Worker Started..."); +console.log("👷 Gitea Deploy Worker Started..."); export interface DeployJobData { repoName: string; @@ -13,16 +12,42 @@ export interface DeployJobData { files: Record; } +// 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 = { + 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( "github-deployments", async (job: Job) => { - const { - repoName, - chatId, - files, - } = job.data; + const { repoName, chatId, files } = job.data; - // 1. Sanitize Repo Name (e.g. "Hono Vite Health Check" -> "hono-vite-health-check") + // 1. Sanitize Repo Name const sanitizedRepoName = repoName .toLowerCase() .trim() @@ -33,110 +58,70 @@ const worker = new Worker( `[Job ${job.id}] Processing deployment for ${sanitizedRepoName} (Chat: ${chatId})...` ); - // 2. Auth - const token = process.env.GITHUB_TOKEN; - const username = process.env.GITHUB_USERNAME; - - if (!token || !username) { - throw new Error("Missing GitHub Token or Username. Cannot deploy."); - } - - // Initialize GitHub Client - const octokit = new Octokit({ auth: token }); - try { await job.updateProgress(10); - // 3. Ensure Repo Exists or Create it - let latestCommitSha: string; + let repoDetails: any; + + const checkRepo = await callGitea("GET", `/repos/${GITEA_USERNAME}/${sanitizedRepoName}`); - try { - // Check if repo exists - await octokit.repos.get({ owner: username, repo: sanitizedRepoName }); - - // If exists, get HEAD commit - const ref = await octokit.git.getRef({ - owner: username, - repo: sanitizedRepoName, - ref: "heads/main", + 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}`, }); - latestCommitSha = ref.data.object.sha; - } catch (e: any) { - if (e.status === 404) { - console.log( - `[Job ${job.id}] Repo not found, creating new repo: ${sanitizedRepoName}` - ); - // Create Repo - await octokit.repos.createForAuthenticatedUser({ - name: sanitizedRepoName, - private: true, // Set to true if you want private repos - auto_init: true, // Creates an initial commit (README) so we have a HEAD - description: `Generated by MWorld for chat ${chatId}`, - }); + if (!createRes.ok) throw new Error("Failed to create repository"); + + repoDetails = await createRes.json(); - // Wait a brief moment for GitHub propagation - await new Promise((r) => setTimeout(r, 2000)); - - // Get the initial commit SHA - const ref = await octokit.git.getRef({ - owner: username, - repo: sanitizedRepoName, - ref: "heads/main", - }); - latestCommitSha = ref.data.object.sha; - } else { - throw e; - } + await new Promise((r) => setTimeout(r, 2000)); + } else { + repoDetails = await checkRepo.json(); } + const defaultBranch = repoDetails.default_branch || "main"; // + await job.updateProgress(30); - // 4. Create Tree (Blobs) - // The API expects an array of file objects. - const treeData = Object.entries(files).map(([path, content]) => ({ - path, // e.g., "src/main.jsx" or "package.json" - mode: "100644" as const, - type: "blob" as const, - content: content as string, // The actual string content from Redis + // 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"), })); - // Create a tree based on the latest commit - const { data: tree } = await octokit.git.createTree({ - owner: username, - repo: sanitizedRepoName, - base_tree: latestCommitSha, - tree: treeData, - }); - await job.updateProgress(60); - // 5. Create Commit - const { data: commit } = await octokit.git.createCommit({ - owner: username, - repo: sanitizedRepoName, - message: `Deploy updates from MWorld ⚡️ (Chat ${chatId})`, - tree: tree.sha, - parents: [latestCommitSha], - }); + // 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", + }, + } + ); - await job.updateProgress(80); - - // 6. Push (Update Reference) - await octokit.git.updateRef({ - owner: username, - repo: sanitizedRepoName, - ref: "heads/main", - sha: commit.sha, - }); + if (!commitRes.ok) { + throw new Error(`Failed to commit files: ${await commitRes.text()}`); + } await job.updateProgress(100); - const repoUrl = `https://github.com/${username}/${sanitizedRepoName}`; + const repoUrl = `${GITEA_API_URL.replace("/api/v1", "")}/${GITEA_USERNAME}/${sanitizedRepoName}`; console.log(`[Job ${job.id}] Deployment Complete! 🚀 URL: ${repoUrl}`); - // TODO: USE SUPABASE TO UPDATE THE REPO URL - return { success: true, url: repoUrl }; } catch (error: any) { console.error(`[Job ${job.id}] Failed:`, error.message); @@ -148,7 +133,7 @@ const worker = new Worker( concurrency: 5, limiter: { max: 10, - duration: 1000, // Rate limit protection for GitHub API + duration: 1000, }, } ); @@ -156,4 +141,4 @@ const worker = new Worker( // Listener for errors worker.on("failed", (job, err) => { console.error(`[Job ${job?.id}] Failed with error ${err.message}`); -}); +}); \ No newline at end of file