diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index fa9ae7e..bc11813 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -32,7 +32,7 @@ jobs: cleanup() { docker rm -f "$cid" >/dev/null 2>&1 || true; } trap cleanup EXIT for attempt in $(seq 1 30); do - if curl --fail --silent http://127.0.0.1:18080/api/health; then exit 0; fi + if curl --fail --silent http://127.0.0.1:18080/api/ready; then exit 0; fi sleep 1 done echo 'Container health endpoint did not become ready' >&2 diff --git a/docs/quality/uygulama-durumu.md b/docs/quality/uygulama-durumu.md index 7013208..b8aa71d 100644 --- a/docs/quality/uygulama-durumu.md +++ b/docs/quality/uygulama-durumu.md @@ -2085,4 +2085,10 @@ Docker runtime image’ında backend’in kullandığı ortak `src/` klasörün ### 2026-09-06 · CI kalite kapıları -GitHub Actions iş akışı eklendi. Her push ve pull request’te test/üretim derlemesi; ayrıca Docker image build’i, nginx yapılandırma testi ve `/api/health` çalışma zamanı kontrolü otomatik çalışacak. Bu iş akışı gerçek production rollout kanıtının yerine geçmez. +GitHub Actions iş akışı eklendi. Her push ve pull request’te test/üretim derlemesi; ayrıca Docker image build’i, nginx yapılandırma testi ve `/api/ready` çalışma zamanı kontrolü otomatik çalışacak. Bu iş akışı gerçek production rollout kanıtının yerine geçmez. + +### 2026-09-06 · Production readiness endpoint’i + +`/api/ready` eklendi. `/api/health` süreç canlılığını korurken readiness kontrolü kullanıcı ve ortak veri dizinlerini gerektiğinde oluşturup okuma/yazma erişimini doğruluyor; başarısızlıkta HTTP 503 ve hangi kontrolün kaldığını döndürüyor. CI container kontrolü artık bu endpoint’i kullanıyor. + +Doğrulama: HTTP readiness testi geçti; toplam test ve build doğrulaması CI’da çalışacak. diff --git a/server/index.js b/server/index.js index 945de38..594e3d1 100644 --- a/server/index.js +++ b/server/index.js @@ -75,6 +75,26 @@ const PORT = process.env.PORT || 3001; const ILAN_BASE = 'https://www.ilan.gov.tr'; const CORS_ORIGINS = String(process.env.CORS_ORIGINS || '').split(',').map(value => value.trim()).filter(Boolean); +function readiness() { + const directories = { + userData: process.env.IHALEIST_USER_DATA_DIR || path.join(__dirname, 'data'), + sharedData: process.env.IHALEIST_SHARED_DATA_DIR || path.join(__dirname, 'data'), + }; + const checks = {}; + let ready = true; + for (const [name, directory] of Object.entries(directories)) { + try { + fs.mkdirSync(directory, { recursive: true }); + fs.accessSync(directory, fs.constants.R_OK | fs.constants.W_OK); + checks[name] = { ok: true }; + } catch (error) { + ready = false; + checks[name] = { ok: false, error: error.code || 'EACCES' }; + } + } + return { ready, checks }; +} + function validateHealthPortfolio(value) { if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Firma profili geçersiz.'); const portfolio = value.scrapedCapabilities?.healthPortfolio; @@ -1700,6 +1720,10 @@ const server = http.createServer(async (req, res) => { } if (url.pathname === '/api/health') return json(res, 200, { ok: true }); + if (url.pathname === '/api/ready') { + const status = readiness(); + return json(res, status.ready ? 200 : 503, { ok: status.ready, ...status }); + } if (url.pathname === '/api/health/sektor') { const say = sektorZenginSayi(); const genel = (cache.tenders || []).filter((t) => t.sektor === 'Genel').length; diff --git a/tests/http-state.test.mjs b/tests/http-state.test.mjs index df67706..845a103 100644 --- a/tests/http-state.test.mjs +++ b/tests/http-state.test.mjs @@ -47,6 +47,14 @@ test('HTTP preparation requires auth, persists draft and rejects stale revisions assert.equal((await request('/api/me/tender-state','PUT',data)).status,409); assert.deepEqual((await request('/api/me/tender-state','GET',null,other)).data.records,{}); }); +test('readiness endpoint checks persistent data directories',async()=>{ + const response=await request('/api/ready','GET',null,null); + assert.equal(response.status,200); + assert.equal(response.data.ok,true); + assert.equal(response.data.ready,true); + assert.equal(response.data.checks.userData.ok,true); + assert.equal(response.data.checks.sharedData.ok,true); +}); test('HTTP documents and API keys expose empty successful state, not endless load errors',async()=>{ const docs=await request('/api/me/documents');assert.equal(docs.status,200);assert.deepEqual(docs.data.belgeler,[]); const keys=await request('/api/me/apikeys');assert.equal(keys.status,200);assert.deepEqual(keys.data.anahtarlar,[]);