const express = require('express'); const { exec } = require('child_process'); const fs = require('fs'); const path = require('path'); const { v4: uuidv4 } = require('uuid'); const app = express(); const PORT = 7860; // CORS middleware app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { return res.sendStatus(200); } next(); }); // JSON body parser app.use(express.json()); // Job storage const jobs = {}; // Web arayüzü app.get('/', (req, res) => { res.send(` APK Build Service

APK Build Service API

POST /build - Build başlat (appName, packageId, repoUrl)

GET /status/:jobId - Durum sorgula

GET /download/:jobId - APK indir

`); }); // Build endpoint app.post('/build', (req, res) => { const { appName, packageId, repoUrl } = req.body; if (!appName || !packageId || !repoUrl) { return res.status(400).json({ success: false, message: 'appName, packageId ve repoUrl gerekli' }); } const jobId = uuidv4(); jobs[jobId] = { status: 'pending', appName, packageId, repoUrl, createdAt: new Date(), apkPath: null }; // Build işlemini arka planda başlat buildAPK(jobId); res.json({ success: true, jobId, message: 'Build başlatıldı' }); }); // Durum sorgulama app.get('/status/:jobId', (req, res) => { const { jobId } = req.params; const job = jobs[jobId]; if (!job) { return res.status(404).json({ success: false, message: 'Job bulunamadı' }); } res.json({ success: true, jobId, status: job.status, appName: job.appName, createdAt: job.createdAt }); }); // APK indirme app.get('/download/:jobId', (req, res) => { const { jobId } = req.params; const job = jobs[jobId]; if (!job) { return res.status(404).json({ success: false, message: 'Job bulunamadı' }); } if (job.status !== 'completed') { return res.status(400).json({ success: false, message: 'APK henüz hazır değil' }); } if (job.apkPath && fs.existsSync(job.apkPath)) { res.download(job.apkPath, `${job.appName}-debug.apk`); } else { res.status(404).json({ success: false, message: 'APK dosyası bulunamadı' }); } }); // Tüm job'ları listele app.get('/jobs', (req, res) => { const jobList = Object.entries(jobs).map(([jobId, job]) => ({ jobId, status: job.status, appName: job.appName, packageId: job.packageId, repoUrl: job.repoUrl, createdAt: job.createdAt, error: job.error })); // Tarihe göre sırala (en yeni önce) jobList.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); res.json({ success: true, jobs: jobList }); }); // Build fonksiyonu function buildAPK(jobId) { const job = jobs[jobId]; job.status = 'building'; const buildDir = `/tmp/build-${jobId}`; // Temizle if (fs.existsSync(buildDir)) { fs.rmSync(buildDir, { recursive: true }); } // Klonla (with token if available) const token = process.env.GITHUB_TOKEN; let cloneUrl = job.repoUrl; if (token && job.repoUrl.startsWith('https://github.com/')) { cloneUrl = job.repoUrl.replace('https://github.com/', `https://git:${token}@github.com/`); } exec(`git clone ${cloneUrl} ${buildDir}`, (error, stdout, stderr) => { if (error) { console.error('Klonlama hatası:', error); job.status = 'failed'; job.error = error.message; return; } // Build işlemi (step by step) - WebView Android App const steps = [ 'npm install', // Android projesi oluştur `mkdir -p android-project/app/src/main/java/com/example/webview`, `mkdir -p android-project/app/src/main/res/values`, `mkdir -p android-project/app/src/main/res/layout`, // AndroidManifest.xml oluştur `cat > android-project/app/src/main/AndroidManifest.xml << 'EOF' EOF`, // MainActivity.java oluştur `cat > android-project/app/src/main/java/com/example/webview/MainActivity.java << 'EOF' package com.example.webview; import android.app.Activity; import android.os.Bundle; import android.webkit.WebView; import android.webkit.WebViewClient; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); WebView webView = findViewById(R.id.webView); webView.setWebViewClient(new WebViewClient()); webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setDomStorageEnabled(true); webView.loadUrl("file:///android_asset/index.html"); } } EOF`, // activity_main.xml oluştur `cat > android-project/app/src/main/res/layout/activity_main.xml << 'EOF' EOF`, // strings.xml oluştur `cat > android-project/app/src/main/res/values/strings.xml << 'EOF' ${job.appName} EOF`, // build.gradle oluştur `cat > android-project/app/build.gradle << 'EOF' plugins { id 'com.android.application' } android { namespace 'com.example.webview' compileSdk 34 defaultConfig { applicationId "com.example.webview" minSdk 21 targetSdk 34 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' } EOF`, // settings.gradle oluştur `cat > android-project/settings.gradle << 'EOF' pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } } rootProject.name = "${job.appName}" include ':app' EOF`, // Web dosyalarını assets klasörüne kopyala `mkdir -p android-project/app/src/main/assets`, `cp -r index.html css js android-project/app/src/main/assets/ 2>/dev/null || cp -r * android-project/app/src/main/assets/`, // Gradle wrapper oluştur `cd android-project && gradle wrapper`, // APK derle `cd android-project && ./gradlew assembleDebug` ]; let currentStep = 0; const runStep = () => { if (currentStep >= steps.length) { // Tüm adımlar başarılı const apkPath = path.join(buildDir, 'android/app/build/outputs/apk/debug/app-debug.apk'); const destPath = `/tmp/${job.appName}-${jobId}-debug.apk`; if (fs.existsSync(apkPath)) { fs.copyFileSync(apkPath, destPath); console.log('APK oluşturuldu:', destPath); job.status = 'completed'; job.apkPath = destPath; } else { job.status = 'failed'; job.error = 'APK dosyası bulunamadı'; } return; } const step = steps[currentStep]; console.log(`Step ${currentStep + 1}: ${step}`); exec(step, { cwd: buildDir, timeout: 600000 }, (err, stdout, stderr) => { if (err) { console.error(`Step ${currentStep + 1} failed:`, err.message); job.status = 'failed'; job.error = `Step ${currentStep + 1} failed: ${err.message}`; return; } if (stdout) console.log(stdout); if (stderr) console.log(stderr); currentStep++; runStep(); }); }; runStep(); }); } app.listen(PORT, () => { console.log(`APK Build Service ${PORT} portunda çalışıyor`); });