From 12b27111680bf3ac9851b97ab7786f9266596b4c Mon Sep 17 00:00:00 2001 From: Do Siki Date: Fri, 11 Sep 2026 21:48:50 +0200 Subject: [PATCH] chore: retire legacy custom CMS (content-editor.js) (MITHOME-93) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the standalone, git-push-based content editor that predates Payload CMS: content-editor.js, its scripts/cms-*.js modules, its scripts/test-content-editor-*.js + scripts/test-cms-publish.js test suite, scripts/markdown-render.js (only used by the editor's guide renderer), the proto-side test doubles (cms-editor-client.test.ts, cms-editor-shortcuts.test.ts), and the editor's own user guide (docs/felhasznaloi-utmutato.md). Kept: proto/src/content/*.json (still the source for migrate-content-to-payload.ts and test fixtures for Header/Footer, per MITHOME-96), proto/src/content/schema.js + scripts/test-content-schema.js (still validate those JSON files), and docs/content-editor-recovery.md (historical incident record, not user-facing tool docs). Safety net before deletion (per user request): added proto/scripts/export-content-snapshot.ts, a reusable Payload Local API exporter, and ran it to produce docs/backups/payload-content-snapshot-*.json — a full hu/en snapshot of every Global + LegalPages + Partners document at the moment of retirement. Also confirmed no data-loss risk otherwise: .content-backups/ (the editor's own gitignored backup dir) tops out at 2026-08-23, well before today's fresh migration run, and every JSON edit ever made through the editor already exists as its own git commit ("content: frissítve a CMS-ből"). Updated dangling references: pre-deploy-tests.sh and .agent/steering/testing.md (dropped the CMS test block), .agent/workflows/deploy.md (publish flow is now Payload draft/publish, not git push), CLAUDE.md + .agent/AGENTS.md (dropped the /cms-feature workflow, deleted alongside it), README.md (stack description), .agent/steering/development-rules.md (the guide-maintenance rule no longer has a guide to maintain). Verified: tsc, lint, proto unit tests (51 passed), root test-content-schema.js, plane-sync unit tests, production build all green after the deletion. Co-Authored-By: Claude Sonnet 5 --- .agent/AGENTS.md | 1 - .agent/steering/development-rules.md | 5 +- .agent/steering/testing.md | 24 +- .agent/workflows/cms-feature.md | 86 -- .agent/workflows/deploy.md | 4 +- CLAUDE.md | 3 +- README.md | 6 +- content-editor.js | 362 ----- ...ent-snapshot-2026-09-11T19-42-31-837Z.json | 1218 +++++++++++++++++ docs/felhasznaloi-utmutato.md | 122 -- proto/scripts/export-content-snapshot.ts | 70 + proto/src/__tests__/cms-editor-client.test.ts | 91 -- .../__tests__/cms-editor-shortcuts.test.ts | 102 -- proto/src/content/schema.js | 8 +- scripts/cms-core.js | 135 -- scripts/cms-diff.js | 65 - scripts/cms-editor-client.js | 345 ----- scripts/cms-editor-shortcuts.js | 56 - scripts/cms-logo-client.js | 345 ----- scripts/cms-logo-page.js | 203 --- scripts/cms-logo.js | 156 --- scripts/cms-pages.js | 325 ----- scripts/cms-publish.js | 43 - scripts/cms-save.js | 65 - scripts/cms-session.js | 66 - scripts/cms-versions.js | 112 -- scripts/markdown-render.js | 98 -- scripts/pre-deploy-tests.sh | 19 +- scripts/test-cms-publish.js | 180 --- scripts/test-content-editor-bottombar.js | 33 - scripts/test-content-editor-conflict.js | 184 --- scripts/test-content-editor-guide.js | 113 -- scripts/test-content-editor-login.js | 169 --- scripts/test-content-editor-logo.js | 189 --- scripts/test-content-editor-logout.js | 90 -- scripts/test-content-editor-save.js | 24 - scripts/test-content-editor-security.js | 33 - scripts/test-content-editor-serializer.js | 108 -- scripts/test-content-editor-version.js | 83 -- scripts/test-content-editor-versions.js | 174 --- 40 files changed, 1316 insertions(+), 4199 deletions(-) delete mode 100644 .agent/workflows/cms-feature.md delete mode 100644 content-editor.js create mode 100644 docs/backups/payload-content-snapshot-2026-09-11T19-42-31-837Z.json delete mode 100644 docs/felhasznaloi-utmutato.md create mode 100644 proto/scripts/export-content-snapshot.ts delete mode 100644 proto/src/__tests__/cms-editor-client.test.ts delete mode 100644 proto/src/__tests__/cms-editor-shortcuts.test.ts delete mode 100644 scripts/cms-core.js delete mode 100644 scripts/cms-diff.js delete mode 100644 scripts/cms-editor-client.js delete mode 100644 scripts/cms-editor-shortcuts.js delete mode 100644 scripts/cms-logo-client.js delete mode 100644 scripts/cms-logo-page.js delete mode 100644 scripts/cms-logo.js delete mode 100644 scripts/cms-pages.js delete mode 100644 scripts/cms-publish.js delete mode 100644 scripts/cms-save.js delete mode 100644 scripts/cms-session.js delete mode 100644 scripts/cms-versions.js delete mode 100644 scripts/markdown-render.js delete mode 100644 scripts/test-cms-publish.js delete mode 100644 scripts/test-content-editor-bottombar.js delete mode 100644 scripts/test-content-editor-conflict.js delete mode 100644 scripts/test-content-editor-guide.js delete mode 100644 scripts/test-content-editor-login.js delete mode 100644 scripts/test-content-editor-logo.js delete mode 100644 scripts/test-content-editor-logout.js delete mode 100644 scripts/test-content-editor-save.js delete mode 100644 scripts/test-content-editor-security.js delete mode 100644 scripts/test-content-editor-serializer.js delete mode 100644 scripts/test-content-editor-version.js delete mode 100644 scripts/test-content-editor-versions.js diff --git a/.agent/AGENTS.md b/.agent/AGENTS.md index ce78461..deaa6c3 100644 --- a/.agent/AGENTS.md +++ b/.agent/AGENTS.md @@ -279,7 +279,6 @@ Az `.agent/workflows/` mappában találhatók az elérhető slash command workfl | --- | --- | | `/new-feature` | Új funkció fejlesztési folyamata | | `/fix-bug` | Hibajavítás folyamata | -| `/cms-feature` | CMS (Content Editor) fejlesztési folyamata | | `/review` | Kód review checklist | | `/deploy` | Deployment folyamata | diff --git a/.agent/steering/development-rules.md b/.agent/steering/development-rules.md index c74b611..764dca1 100644 --- a/.agent/steering/development-rules.md +++ b/.agent/steering/development-rules.md @@ -76,9 +76,8 @@ git commit -m "fix(): " ## Felhasználói dokumentáció karbantartása -- A CMS **❓ Súgó** menüpontja a `docs/felhasznaloi-utmutato.md` fájlt rendereli (`/guide`). -- **Kötelező**: minden CMS- vagy honlapfunkció változtatásánál (új gomb, viselkedésváltozás, útvonal) ugyanabban a commitban frissítsd az útmutatót. -- Támogatott markdown forma a `scripts/markdown-render.js` részhalmaza: címsorok, **félkövér**, `kód`, listák, linkek, `---` elválasztók (táblázat nem). +- A régi egyedi CMS-nek (`content-editor.js`, saját **❓ Súgó** menüponttal, `docs/felhasznaloi-utmutato.md`) volt saját, karbantartott felhasználói útmutatója — ezt a Payload CMS-re állás után leépítettük (MITHOME-93). A Payload admin felület (`/admin`) saját, upstream dokumentációval rendelkezik; ügyfél-facing Payload-specifikus útmutató készítése külön feladat (MITHOME-95), ha szükséges. +- **Kötelező**: minden honlapfunkció-változtatásnál (új útvonal, viselkedésváltozás) ellenőrizd, hogy a `docs/` alatti releváns dokumentáció ne legyen elavult. ## Nyelvhasználat diff --git a/.agent/steering/testing.md b/.agent/steering/testing.md index 392541f..3595700 100644 --- a/.agent/steering/testing.md +++ b/.agent/steering/testing.md @@ -77,22 +77,12 @@ Minden deploy előtt az **egyetlen belépési pont** futtatandó: scripts/pre-deploy-tests.sh ``` -Ez lefedi: proto unit + `tsc --noEmit` + `eslint`, content séma-validáció, a CMS -integrációs tesztjei (`scripts/test-content-editor-*.js`), a publish-teszt és a +Ez lefedi: proto unit + `tsc --noEmit` + `eslint`, content séma-validáció és a Plane-sync unit tesztek. Bármelyik hibája megszakítja a kiadást. -A CMS (content-editor.js) tesztjei külön is futtathatók (valódi szervert indítanak): - -```bash -node scripts/test-content-editor-security.js # auth, CSRF, XFF -node scripts/test-content-editor-serializer.js # collect/reindex regresszió -node scripts/test-content-editor-save.js # atomikus mentés + validáció -node scripts/test-content-editor-conflict.js # optimista zárolás (409) -node scripts/test-content-editor-versions.js # verziók panel (diff, restore) -node scripts/test-content-editor-logo.js # logó feltöltés -node scripts/test-content-editor-login.js # login flow (session, Safari) -node scripts/test-content-editor-logout.js # logout + rate-limit -node scripts/test-content-editor-guide.js # /guide + markdown renderer -node scripts/test-content-editor-bottombar.js # alsó sáv layout guard -node scripts/test-cms-publish.js # publish parancs + integráció -``` +> A régi egyedi CMS (content-editor.js) saját teszt-szkriptjeit (auth, CSRF, +> serializer, optimista zárolás, verziók panel, logó feltöltés, login/logout, +> guide, publish) a Payload CMS-re állás után (MITHOME-93) eltávolítottuk — +> a Payload admin felület a saját upstream tesztelésével fedett, ezt itt nem +> duplikáljuk. Payload collection/global konfigurációk saját tesztlefedettsége +> külön feladat (MITHOME-96), még nincs implementálva. diff --git a/.agent/workflows/cms-feature.md b/.agent/workflows/cms-feature.md deleted file mode 100644 index 50621a8..0000000 --- a/.agent/workflows/cms-feature.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -description: CMS (Content Editor) fejlesztési munkafolyamata — módosítástól a staging élesítésig ---- - -# CMS Fejlesztési Workflow (/cms-feature) - -A `content-editor.js`, a hozzá tartozó scriptek (`scripts/markdown-render.js`, `scripts/test-content-editor-*.js`) és a CMS-en keresztül szerkeszthető tartalmi sémák (`proto/src/content/schema.js`) módosításakor kövesd ezt a folyamatot. - -A szabály (a *miért*): `.agent/steering/development-rules.md` → „Felhasználói dokumentáció karbantartása". Ez a fájl a *hogyan*-t írja le. - ---- - -## 1. Tervezés - -- Fogalmazd meg, mi változik a felhasználó szemszögéből (új gomb? más viselkedés? új hibaüzenet?) -- **Új endpoint vagy UI elem esetén**: tervezd meg az útmutató érintett szakaszának frissítését is. -- Content séma (`schema.js`) változásnál ellenőrizd, hogy a meglévő JSON fájlok mind átmegyek az új sémán. - -## 2. Implementáció - -- A CMS system Nodeon fut, **nincs node_modules** — csak beépítő modulokat használj (`http`, `fs`, `path`, `crypto`, `child_process`). -- Fájlméret: a `content-editor.js` közelít a 300 soros soft limit felé — nagyobb funkciót szervezz ki külön modulba (pl. `scripts/markdown-render.js` minta). -- Biztonsági követelmények: - - Minden új POST endpoint **CSRF token ellenőrzéssel**. - - Új GET endpoint **auth után** serviced (kivétel: `/logout` minta). - - User input escape-elés (lásd `escapeHtml` a markdown rendererben). - - Titok soha nem kerül kimenetre — válaszkóddal (401/200) igazolj. - -## 3. Útmutató frissítése (KÖTELEZŐ, ugyanabban a commitban) - -- Forrás: `docs/felhasznaloi-utmutato.md` — a felhasználó nyelvén, nem műszaki részletekkel. -- Új UI elem → felület áttekintés szakasz + adott funkció szakasz frissítése. -- Támogatott markdown részhalmaz: címsorok, **félkövér**, `kód`, listák, linkek, `---` (táblázat nem — a `scripts/markdown-render.js` nem jeleníti meg). - -## 4. Tesztek - -Minden CMS-változtatás után futtasd mindet: - -```bash -node --check content-editor.js -node scripts/test-content-editor-security.js # auth, CSRF, XFF -node scripts/test-content-editor-serializer.js # collect/reindex regresszió -node scripts/test-content-editor-save.js # atomic save + validáció -node scripts/test-content-editor-logout.js # /logout viselkedés -node scripts/test-content-editor-guide.js # /guide + Súgó link -``` - -- Új funkcióhoz **új tesztfájl** a fenti minta szerint (valós szervert indító integration teszt ajánlott). -- Content séma változásnál: `node scripts/test-content-schema.js` is. - -## 5. Commit - -```bash -git add content-editor.js scripts/ docs/felhasznaloi-utmutato.md -git commit -m "feat(cms): " # vagy fix(cms): -``` - -- Az útmutató frissítése **ugyanabban a commitban** landol, mint a funkció. -- Plane ticket (`Closes MITHOME-XX`) + `node plane-sync.js --yes` a TODO.md szinkronhoz. - -## 6. Staging élesítés és ellenőrzés - -A CMS a websitetől **független szolgáltatás** — deploy szkript nélkül, közvetlenül élesítjük: - -```bash -ssh sadmin@llmdev.mozdit.hu ' - cd /home/sadmin/websitedev && git pull --ff-only origin main && - sudo systemctl restart mozdit-content-editor.service && - systemctl is-active mozdit-content-editor.service' -``` - -Ellenőrzés (a hitelesítő adatokat az `/etc/mozdit-content-editor.env`-ből olvasd, **soha ne írd ki**): - -```bash -# kulcs nélkül 401-et várunk -curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:4001/ -# hitelesített kérést sudo sh -c ". /etc/mozdit-content-editor.env; curl -u ..." mintával futtass -``` - -- Az érintett funkciókra vonatkozó válaszkódok ellenőrzése (401/200/429 stb.). -- Ha a honlap (`proto/`) is változott: `./scripts/deploy_to_stage_on_local.sh` a teljes deployhoz. - -## 7. Bezárás - -- Plane ticket → Done. -- `node plane-sync.js --yes` + `git commit -m "sync: ..."` + push. diff --git a/.agent/workflows/deploy.md b/.agent/workflows/deploy.md index 37c3a44..7f105b9 100644 --- a/.agent/workflows/deploy.md +++ b/.agent/workflows/deploy.md @@ -45,9 +45,9 @@ Bármely lépés hibája azonnal leállítja a folyamatot. Paraméterek környez 2. `docker compose -f docker-compose..yml up --build --force-recreate -d` — a friss Dockerfile/build-arg változások is érvényesüljenek. 3. Healthcheck: max 60s-ig kérdezi a `http://localhost:/api/health`-t (staging: 8081, prod: 8080). Sikertelenség esetén app-logok kiírása + exit 1. -## CMS-ből történő publikálás +## Tartalom publikálása (Payload admin) -A Content Editor (`content-editor.js`) Publikálás gombja: commit + push (`content: frissítve a CMS-ből`), majd a `CMS_DEPLOY_ENV`-ben beállított környezet deployja a háttérben. **Soha nem deployol productionre implicit** — a `CMS_DEPLOY_ENV` kötelező, érvényes érték nélkül az editor el sem indul. +A régi Content Editor (`content-editor.js`, git push-alapú publikálás) helyét a Payload admin (`/admin`) draft/publish + verziózás funkciója vette át (MITHOME-92/93) — ez nem jár git commit-tal vagy automatikus deployjal, a tartalom közvetlenül a MongoDB-be íródik. Kódváltozás (Globals/Collections séma, frontend) továbbra is a lenti git-alapú deploy folyamaton megy át. ## Production deploy (élesítés) diff --git a/CLAUDE.md b/CLAUDE.md index 41667b0..9733a42 100755 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,7 +59,7 @@ websitedev/ ├── .agent/ # AI szabályrendszer ← OLVASD EL │ ├── AGENTS.md # Elsődleges szabályok │ ├── steering/ # Auto-betöltődő irányelvek -│ ├── workflows/ # /new-feature, /fix-bug, /cms-feature, /review, /deploy +│ ├── workflows/ # /new-feature, /fix-bug, /review, /deploy │ └── references/ # Accessibility checklist ├── TODO.md # Feladatlista (Plane szinkron) └── plane-sync.js # Plane szinkronizáló script @@ -98,7 +98,6 @@ Szükséges változók (`.env` és `proto/.env.local`): | `.agent/steering/testing.md` | Tesztelési stratégia, coverage elvárások | | `.agent/workflows/new-feature.md` | Új funkció fejlesztési lépései | | `.agent/workflows/fix-bug.md` | Hibajavítás lépései (TDD) | -| `.agent/workflows/cms-feature.md` | CMS fejlesztés + útmutató karbantartás | | `.agent/workflows/review.md` | Code review checklist | | `.agent/workflows/deploy.md` | Deployment folyamat | | `.agent/references/accessibility-checklist.md` | WCAG 2.1 AA ellenőrzőlista | \ No newline at end of file diff --git a/README.md b/README.md index d286eeb..d46dc08 100755 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ Modern Next.js weboldal a mozdIT Bt. számára — webtárhely, email- és DNS-s ## Stack - **Frontend**: Next.js 15 (Turbopack), React 19, TypeScript, Tailwind CSS 4 -- **Tartalom**: JSON-alapú, séma-validált content rendszer (`proto/src/content/`) -- **CMS**: saját, dependency-mentes `content-editor.js` (böngészős szerkesztő) +- **CMS**: Payload CMS (self-hosted, `/admin`), MongoDB adapter, draft/publish + verziózás, hu/en lokalizáció +- **Tartalom**: Payload Globals/Collections (a korábbi JSON-alapú content rendszer, `proto/src/content/`, csak a migrációs script forrásaként és teszt-fixture-ként él tovább — MITHOME-91/93) - **Backend**: Next.js API routes, MongoDB -- **Tesztelés**: Jest, React Testing Library, Playwright (smoke), valódi szervert indító CMS-tesztek +- **Tesztelés**: Jest, React Testing Library, Playwright (smoke) - **Deploy**: natív Docker Compose (`deploy.sh`) + Gitea Actions nélkül, lokálisan vezérelt - **Monitoring**: Winston + Loki, plusz `scripts/security-scan.sh` (ntfy riasztással) diff --git a/content-editor.js b/content-editor.js deleted file mode 100644 index 416e982..0000000 --- a/content-editor.js +++ /dev/null @@ -1,362 +0,0 @@ -#!/usr/bin/env node - -/** - * mozdIT Content Editor Server v2 - * Szerkesztő felület a JSON tartalom fájlokhoz - * Támogatja: szöveg szerkesztés, tömbelem hozzáadás/törlés - * Futtatás: node content-editor.js - * Megnyitás: http://localhost:4001 - */ - -const http = require('http'); -const fs = require('fs'); -const path = require('path'); -const { exec, execSync } = require('child_process'); -const crypto = require('crypto'); -const { validateContent } = require('./proto/src/content/schema'); -const { renderMarkdown } = require('./scripts/markdown-render'); -const { buildPublishCommand, interpretPublishResult } = require('./scripts/cms-publish'); -const { handleVersionRoutes, listVersions } = require('./scripts/cms-versions'); -const { handleSaveRoute } = require('./scripts/cms-save'); -const { LOGO_TARGETS, handleLogoRoutes } = require('./scripts/cms-logo'); - -const PORT = Number(process.env.CONTENT_EDITOR_PORT) || 4001; -// WHY: overridable so the publish integration test can run against a throwaway -// git clone instead of the real repository. -const CONTENT_DIR = process.env.CONTENT_EDITOR_CONTENT_DIR || path.join(__dirname, 'proto', 'src', 'content'); -const BACKUP_DIR = path.join(__dirname, '.content-backups'); -const MAX_REQUEST_BODY_BYTES = 256 * 1024; -const AUDIT_LOG_FILE = process.env.CONTENT_EDITOR_AUDIT_FILE || path.join(__dirname, '.content-editor-audit.jsonl'); -const GUIDE_FILE = process.env.CONTENT_EDITOR_GUIDE_FILE || path.join(__dirname, 'docs', 'felhasznaloi-utmutato.md'); -const RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000; -const AUTH_MAX_ATTEMPTS = 5; -const PUBLISH_MAX_ATTEMPTS = 3; -let isPublishing = false; - -const FILES = { - common: path.join(CONTENT_DIR, 'common.json'), - home: path.join(CONTENT_DIR, 'pages', 'home.json'), - about: path.join(CONTENT_DIR, 'pages', 'about.json'), - services: path.join(CONTENT_DIR, 'pages', 'services.json'), - contact: path.join(CONTENT_DIR, 'pages', 'contact.json'), - adatvedelem: path.join(CONTENT_DIR, 'pages', 'adatvedelem.json'), - hasznalatiFeltetelek: path.join(CONTENT_DIR, 'pages', 'hasznalati-feltetelek.json'), -}; - -const FILE_LABELS = { - common: '⚙️ Közös szövegek', - home: '🏠 Kezdőlap', - about: '👥 Rólunk', - services: '🛠️ Szolgáltatások', - contact: '📬 Kapcsolat', - adatvedelem: '🔒 Adatvédelem', - hasznalatiFeltetelek: '⚖️ ÁSZF', -}; - -const { HTML, GUIDE_PAGE, LOGIN_PAGE, VERSIONS_PAGE } = require('./scripts/cms-pages'); -const { LOGO_PAGE } = require('./scripts/cms-logo-page'); -const { validateLogin, createSessionCookie, clearSessionCookie, hasValidSession, deleteSession } = require('./scripts/cms-session'); - -// Browser script is kept in its own file and inlined into the HTML template at render time. -const clientJs = fs.readFileSync(path.join(__dirname, 'scripts', 'cms-editor-client.js'), 'utf8') - + '\n' + fs.readFileSync(path.join(__dirname, 'scripts', 'cms-editor-shortcuts.js'), 'utf8'); - - -// ── Server ─────────────────────────────────────────────────────────────────── - -// Security/infra helpers live in scripts/cms-core.js (file-size limits). -const core = require('./scripts/cms-core'); -const { CMS_USER, CMS_PASS, CMS_DEPLOY_ENV, CSRF_TOKEN, securityConfigIsValid, getClientAddress, hasValidCsrfToken, backupAndWriteAtomically } = core; -const exceedsRateLimit = (key, limit) => core.exceedsRateLimit(key, limit, RATE_LIMIT_WINDOW_MS); -const isRateLimited = (key, limit) => core.isRateLimited(key, limit, RATE_LIMIT_WINDOW_MS); -const recordRateLimitAttempt = key => core.recordRateLimitAttempt(key, RATE_LIMIT_WINDOW_MS); -const hasValidCredentials = req => core.hasValidCredentials(req, validateLogin); -const isAuthenticated = core.makeIsAuthenticated(hasValidSession, validateLogin); -const isBrowserNavigation = core.isBrowserNavigation; -const writeAudit = core.makeWriteAudit(AUDIT_LOG_FILE); - -// Deploy version = git short SHA of the checked-out commit. Read once at startup: -// a CMS "deploy" is git pull + service restart, so this identifies the running code. -function readDeployVersion() { - try { - return execSync('git rev-parse --short HEAD', { cwd: __dirname, encoding: 'utf8' }).trim(); - } catch { - return 'unknown'; - } -} -const DEPLOY_VERSION = readDeployVersion(); - -const server = http.createServer(async (req, res) => { - res.setHeader('X-Frame-Options', 'DENY'); - res.setHeader('X-Content-Type-Options', 'nosniff'); - const clientAddress = getClientAddress(req); - if (!securityConfigIsValid()) { - res.writeHead(503, { 'Content-Type': 'text/plain; charset=utf-8' }); - res.end('Content Editor is disabled: CMS_USER and CMS_PASS must be configured.'); - return; - } - - const u = new URL(req.url, `http://localhost:${PORT}`); - - // WHY: Basic Auth credentials are cached by the browser until it closes, so there is - // no native logout. The client calls /logout with deliberately invalid credentials, - // which overwrites the cached pair; the next navigation prompts for login again. - // Deliberately exempt from the auth rate limiter so logging out never locks the user out. - if (u.pathname === '/logout' && req.method === 'GET') { - // Legacy cache-buster endpoint; no WWW-Authenticate — Safari would show its - // native auth dialog on any fetch hitting this challenge. - res.writeHead(401, { 'Cache-Control': 'no-store' }); - res.end('Logged out'); - return; - } - - // Public: logo asset for the login page. - if (req.method === 'GET' && u.pathname === '/logo.png') { - try { - // ?variant=header serves the website header logo (branding page preview). - const file = u.searchParams.get('variant') === 'header' ? LOGO_TARGETS.header : LOGO_TARGETS.icon; - const logo = fs.readFileSync(path.join(__dirname, 'proto', 'public', file)); - res.writeHead(200, { 'Content-Type': 'image/png', 'Cache-Control': 'public, max-age=3600' }); - res.end(logo); - } catch { - res.writeHead(404); res.end('Not found'); - } - return; - } - - // Public: deploy version (git SHA only — no secrets) for quick "is the fix live?" checks. - if (req.method === 'GET' && u.pathname === '/version') { - res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); - res.end(JSON.stringify({ version: DEPLOY_VERSION, env: CMS_DEPLOY_ENV })); - return; - } - - // Public: styled login page (shown after logout and for unauthenticated browser visits). - if (req.method === 'GET' && u.pathname === '/login') { - res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }); - res.end(LOGIN_PAGE()); - return; - } - - // Public: login form endpoint. Shares the auth rate-limit budget with failed - // Basic attempts so the form cannot be brute-forced either. - if (req.method === 'POST' && u.pathname === '/login') { - let body = ''; - let bodyTooLarge = false; - req.on('data', c => { - if (body.length + c.length > 1024) { bodyTooLarge = true; return; } - body += c; - }); - req.on('end', () => { - if (isRateLimited(`auth:${clientAddress}`, AUTH_MAX_ATTEMPTS)) { - writeAudit('login_failed', { clientAddress, result: 'rate_limited' }); - res.writeHead(429, { 'Content-Type': 'application/json', 'Retry-After': String(RATE_LIMIT_WINDOW_MS / 1000) }); - res.end(JSON.stringify({ ok: false, error: 'Túl sok belépési kísérlet — próbáld újra később.' })); - return; - } - let user = ''; - let pass = ''; - try { - const parsed = JSON.parse(body); - user = String(parsed.user || ''); - pass = String(parsed.pass || ''); - } catch { /* empty credentials fail validation below */ } - if (!bodyTooLarge && validateLogin(user, pass, CMS_USER, CMS_PASS)) { - // WHY: successful logins must not consume the failure budget — tests and - // multi-tab users log in repeatedly and would lock themselves out. - const isSecure = req.headers['x-forwarded-proto'] === 'https'; - writeAudit('login_success', { clientAddress }); - res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': createSessionCookie(isSecure) }); - res.end(JSON.stringify({ ok: true })); - return; - } - recordRateLimitAttempt(`auth:${clientAddress}`); - writeAudit('login_failed', { clientAddress, result: bodyTooLarge ? 'request_too_large' : 'invalid_credentials' }); - res.writeHead(401, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ ok: false, error: 'Hibás felhasználónév vagy jelszó.' })); - }); - return; - } - - if (isRateLimited(`auth:${clientAddress}`, AUTH_MAX_ATTEMPTS)) { - writeAudit('authentication_failed', { clientAddress, limited: true }); - res.writeHead(429, { 'Retry-After': String(RATE_LIMIT_WINDOW_MS / 1000) }); - res.end('Too many authentication attempts'); - return; - } - if (!isAuthenticated(req)) { - recordRateLimitAttempt(`auth:${clientAddress}`); - writeAudit('authentication_failed', { clientAddress, limited: false }); - // Browser navigations land on the styled login page; API/curl gets a plain 401. - // WHY no WWW-Authenticate: Safari pops its native auth dialog on fetch() calls - // that receive a Basic challenge — the styled /login page handles browsers. - if (isBrowserNavigation(req)) { - res.writeHead(302, { Location: '/login', 'Cache-Control': 'no-store' }); - res.end(); - return; - } - res.writeHead(401, { 'Cache-Control': 'no-store' }); - res.end('Access denied'); - return; - } - - const fileKey = u.searchParams.get('file') || 'home'; - const activeFile = FILES[fileKey] ? fileKey : 'home'; - - if (req.method === 'POST' && !hasValidCsrfToken(req)) { - writeAudit('csrf_rejected', { clientAddress, path: u.pathname, file: activeFile }); - res.writeHead(403, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ ok: false, error: 'Érvénytelen vagy hiányzó CSRF token' })); - return; - } - - // POST /logout — invalidate the browser session (Basic Auth stays valid by design). - if (req.method === 'POST' && u.pathname === '/logout') { - deleteSession(req); - writeAudit('logout', { clientAddress, user: CMS_USER }); - res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': clearSessionCookie() }); - res.end(JSON.stringify({ ok: true })); - return; - } - - // GET /guide — user guide rendered from the maintained markdown in the repo. - if (req.method === 'GET' && u.pathname === '/guide') { - let contentHtml; - try { - contentHtml = renderMarkdown(fs.readFileSync(GUIDE_FILE, 'utf8')); - } catch (error) { - contentHtml = '

Az útmutató jelenleg nem elérhető. Kérlek, szólj a fejlesztőnek.

'; - } - res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); - res.end(GUIDE_PAGE(contentHtml)); - return; - } - - // POST /save — handled in scripts/cms-save.js (optimistic lock + validation + backup). - if (handleSaveRoute({ - req, res, u, activeFile, files: FILES, maxBodyBytes: MAX_REQUEST_BODY_BYTES, - validate: validateContent, writeAudit, backupAndWrite: backupAndWriteAtomically, - backupDir: BACKUP_DIR, user: CMS_USER, clientAddress, cmsDirname: __dirname, - })) return; - - // POST /publish — Git Commit, Pull Rebase & Push - if (req.method === 'POST' && u.pathname === '/publish') { - if (isPublishing) { - res.writeHead(423, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ ok: false, error: 'Már folyamatban van egy publikálás. Kérlek, várj.' })); - return; - } - isPublishing = true; - if (exceedsRateLimit(`publish:${clientAddress}`, PUBLISH_MAX_ATTEMPTS)) { - isPublishing = false; - writeAudit('publish_rate_limited', { clientAddress, user: CMS_USER }); - res.writeHead(429, { 'Content-Type': 'application/json', 'Retry-After': String(RATE_LIMIT_WINDOW_MS / 1000) }); - res.end(JSON.stringify({ ok: false, error: 'Túl sok publikálási kísérlet' })); - return; - } - - // Command shape and result classification live in scripts/cms-publish.js - // (WHY comments there): commit only when staged changes exist, rebase with - // abort-on-failure, deterministic no-changes marker instead of output matching. - exec(buildPublishCommand('content: frissítve a CMS-ből'), { cwd: CONTENT_DIR }, (error, stdout, stderr) => { - isPublishing = false; - res.writeHead(200, { 'Content-Type': 'application/json' }); - const outcome = interpretPublishResult(error, stdout, stderr); - writeAudit('publish_finished', { clientAddress, user: CMS_USER, result: outcome.result }); - if (!outcome.ok) { - res.end(JSON.stringify({ ok: false, error: outcome.error })); - return; - } - // Deploy only when content actually changed — a no-op publish must not - // trigger a rebuild. Deploy only the explicitly configured environment; - // never default to production. Overridable for tests. - if (outcome.hadChanges) { - // WHY direct child instead of a detached `cmd &`: under the systemd unit's - // hardening (NoNewPrivileges/PrivateTmp) the backgrounded grandchild died - // silently (observed twice: stale site after a publish). A direct child is - // not detached, runs to completion, and the callback turns the audit entry - // into a real "deploy finished/failed" signal. The HTTP response is already - // sent; deploy output goes to deploy.log so the pipes stay quiet. - const deployCmd = process.env.CONTENT_EDITOR_DEPLOY_CMD - || `cd ../../../ && ./deploy.sh ${CMS_DEPLOY_ENV} > deploy.log 2>&1`; - writeAudit('deploy_spawned', { clientAddress, user: CMS_USER, env: CMS_DEPLOY_ENV }); - // WHY cwd: without it the child starts in the process working directory - // (repo root), where `cd ../../../` lands on "/" — no write access, so - // deploy.log creation failed with Permission denied and the deploy never - // ran. CONTENT_DIR is the same base the git publish command uses. - exec(deployCmd, { cwd: CONTENT_DIR, maxBuffer: 8 * 1024 * 1024 }, deployError => { - writeAudit('deploy_exec_exit', { - clientAddress, - user: CMS_USER, - result: deployError ? 'error' : 'ok', - error: deployError ? String(deployError.message).slice(0, 300) : undefined, - }); - }); - } - res.end(JSON.stringify({ ok: true, output: outcome.output })); - }); - return; - } - - // GET /versions + POST /restore — handled in scripts/cms-versions.js. - if (handleVersionRoutes({ - req, res, u, activeFile, - backupDir: BACKUP_DIR, - currentFile: FILES[activeFile], - validate: validateContent, - writeAudit, clientAddress, user: CMS_USER, - versionsPage: (fileKey, diff) => VERSIONS_PAGE(fileKey, FILE_LABELS[fileKey] || fileKey, listVersions(BACKUP_DIR, fileKey), diff, CSRF_TOKEN), - })) return; - - // GET /branding + POST /logo — handled in scripts/cms-logo.js. - if (handleLogoRoutes({ - req, res, u, - publicDir: path.join(__dirname, 'proto', 'public'), - backupDir: BACKUP_DIR, - writeAudit, clientAddress, user: CMS_USER, - logoPage: () => LOGO_PAGE(CSRF_TOKEN), - })) return; - - // GET / — editor UI - let message = null; - let jsonData = '{}'; - try { - jsonData = fs.readFileSync(FILES[activeFile], 'utf8').trim(); - } catch (e) { - message = { type: 'err', text: 'Fájl olvasási hiba: ' + e.message }; - } - // WHY: fingerprint of the file content at page load. The editor sends it back - // on save (X-Content-Hash); a mismatch means the file changed since this tab - // was opened (deploy, another tab, git) and a blind save would silently - // overwrite those changes. - const contentHash = crypto.createHash('sha256').update(jsonData).digest('hex'); - - res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }); - res.end(HTML(activeFile, jsonData, message, CSRF_TOKEN, FILE_LABELS, clientJs, contentHash, DEPLOY_VERSION)); -}); - -if (require.main === module) { - if (!securityConfigIsValid()) { - throw new Error('CMS_USER, CMS_PASS és érvényes CMS_DEPLOY_ENV nélkül a Content Editor nem indítható el.'); - } - server.listen(PORT, '127.0.0.1', () => { - writeAudit('startup', { version: DEPLOY_VERSION, env: CMS_DEPLOY_ENV }); - console.log(`\n✅ mozdIT Content Editor fut: http://localhost:${PORT} (v${DEPLOY_VERSION})\n`); - console.log(' Szerkeszthető fájlok:'); - Object.entries(FILE_LABELS).forEach(([k, l]) => { - const rel = k === 'common' ? 'common.json' : `pages/${k}.json`; - console.log(` • ${l}: proto/src/content/${rel}`); - }); - console.log('\n Ctrl+C a leállításhoz\n'); - }); -} - -module.exports = { - backupAndWriteAtomically, - validateContent, - hasValidCredentials, - hasValidCsrfToken, - getClientAddress, - securityConfigIsValid, - csrfToken: CSRF_TOKEN, -}; diff --git a/docs/backups/payload-content-snapshot-2026-09-11T19-42-31-837Z.json b/docs/backups/payload-content-snapshot-2026-09-11T19-42-31-837Z.json new file mode 100644 index 0000000..cb31705 --- /dev/null +++ b/docs/backups/payload-content-snapshot-2026-09-11T19-42-31-837Z.json @@ -0,0 +1,1218 @@ +{ + "exportedAt": "2026-09-11T19:42:31.837Z", + "reason": "MITHOME-93 — régi CMS (content-editor.js) leépítése előtti biztonsági mentés", + "globals": { + "home": { + "hu": { + "createdAt": "2026-09-10T01:27:45.860Z", + "updatedAt": "2026-09-11T19:34:58.224Z", + "globalType": "home", + "hero": { + "title": "Webtárhely, üzleti e-mail és domainkezelés egy helyen", + "subtitle": "Kis ügyfélkör, nagy figyelem: stabil tárhely, üzembiztos levelezés és DNS adminisztráció — gyors reakcióval.", + "description": "Stabil webtárhelyet, üzleti levelezést és domainkezelést biztosítunk, személyes műszaki támogatással – hogy Ön a vállalkozására koncentrálhasson.", + "trustBullets": [ + { + "value": "Saját dedikált infrastruktúra", + "id": "6aa4526208a0db8b7fc187a4" + }, + { + "value": "Webtárhely, domain, e-mail egy helyen", + "id": "6aa4526208a0db8b7fc187a5" + }, + { + "value": "Kis- és középvállalkozásokra szabva", + "id": "6aa4526208a0db8b7fc187a6" + } + ], + "cta": { + "primary": { + "text": "Kérjen személyre szabott konzultációt", + "href": "/kapcsolat", + "external": false + }, + "secondary": { + "text": "Webmail", + "href": "https://mail.mozdit.hu", + "external": true + } + } + }, + "about": { + "title": "Miért érdemes minket választani?", + "usps": [ + { + "id": "egyszeru", + "title": "Egyszerűen kezelhető szolgáltatások", + "description": "Nincs technoblabla. Átlátható és könnyen használható rendszereket biztosítunk, hogy Ön a saját üzletére fókuszálhasson.", + "icon": "🎯" + }, + { + "id": "nemtomeg", + "title": "Nem tömegszolgáltatás", + "description": "Egyedi figyelem és rugalmas, személyre szabott csomagok, amelyek tökéletesen igazodnak a vállalkozása igényeihez.", + "icon": "👤" + }, + { + "id": "egykezben", + "title": "Minden egy kézben", + "description": "Domain, webtárhely és üzleti e-mail szolgáltatások zökkenőmentes kezelése, mindent egyetlen megbízható helyről.", + "icon": "🧩" + }, + { + "id": "modern_tech", + "title": "Modern és Biztonságos", + "description": "TLS/SSL titkosítással, rendszeres frissítésekkel, biztonsági mentésekkel és SSD-alapú infrastruktúrával dolgozunk a szolgáltatások biztonságos és gyors működéséért.", + "icon": "🚀" + } + ] + }, + "services": { + "title": "Szolgáltatásaink", + "subtitle": "Komplex megoldásokat kínálunk, amelyek tökéletesen igazodnak a vállalkozás igényeihez.", + "items": [ + { + "id": "hosting", + "title": "Webtárhely (Hosting)", + "description": "Gyors, saját infrastruktúrán működő webtárhely rendszeres biztonsági mentéssel és folyamatos felügyelettel, hogy weboldala megbízhatóan elérhető maradjon.", + "icon": "🔥", + "features": [ + { + "value": "Éves szinten 99,9%-os rendelkezésre állási cél, tervezett karbantartások kivételével", + "id": "6aa4526208a0db8b7fc187a7" + }, + { + "value": "SSD alapú gyors kiszolgálás", + "id": "6aa4526208a0db8b7fc187a8" + }, + { + "value": "24/7 rendszeres monitoring", + "id": "6aa4526208a0db8b7fc187a9" + }, + { + "value": "Automatikus backup rendszer", + "id": "6aa4526208a0db8b7fc187aa" + } + ], + "ctaText": "Webtárhely-ajánlatot kérek" + }, + { + "id": "email", + "title": "E-mail szolgáltatás", + "description": "Saját domainhez tartozó üzleti e-mail-címek megbízható levélkezeléssel, hatékony spam- és vírusvédelemmel, webmailen vagy megszokott levelezőprogramjából.", + "icon": "📧", + "features": [ + { + "value": "Saját domain alapú e-mail címek", + "id": "6aa4526208a0db8b7fc187ab" + }, + { + "value": "Titkosított webmail- és IMAP-hozzáférés a levelek biztonságos eléréséhez", + "id": "6aa4526208a0db8b7fc187ac" + }, + { + "value": "Erős spam- és vírusvédelem", + "id": "6aa4526208a0db8b7fc187ad" + } + ], + "ctaText": "E-mail szolgáltatásról érdeklődöm" + }, + { + "id": "domain", + "title": "Domain és DNS", + "description": "Domainregisztrációt, hosszabbítást és DNS-kezelést intézünk egy helyen, hogy weboldala és levelezése megfelelően legyen beállítva.", + "icon": "🌐", + "features": [ + { + "value": "Magyar és nemzetközi domainek", + "id": "6aa4526208a0db8b7fc187ae" + }, + { + "value": "Biztonságos DNS kezelés", + "id": "6aa4526208a0db8b7fc187af" + }, + { + "value": "Levelezési DNS-beállítások: SPF, DKIM és DMARC konfiguráció a hitelesebb levélküldésért és a hamisítás elleni védelem támogatásához", + "id": "6aa4526208a0db8b7fc187b0" + }, + { + "value": "Gyors ügyintézés", + "id": "6aa4526208a0db8b7fc187b1" + } + ], + "ctaText": "Domain- és DNS-segítséget kérek" + } + ] + }, + "cta": { + "title": "Kérjen személyre szabott ajánlatot", + "subtitle": "Írja meg röviden, mire van szüksége; segítünk kiválasztani a vállalkozásához illő tárhely-, levelezési vagy domainmegoldást.", + "button": "Kérjen ajánlatot" + }, + "serviceFeatures": { + "title": "A szolgáltatás tartalmazza:" + }, + "_status": "published", + "id": "6aa20791fee88ba6840c429a" + }, + "en": { + "createdAt": "2026-09-10T01:27:45.860Z", + "updatedAt": "2026-09-11T19:34:58.224Z", + "globalType": "home", + "hero": { + "title": "Webtárhely, üzleti e-mail és domainkezelés egy helyen", + "subtitle": "Kis ügyfélkör, nagy figyelem: stabil tárhely, üzembiztos levelezés és DNS adminisztráció — gyors reakcióval.", + "description": "Stabil webtárhelyet, üzleti levelezést és domainkezelést biztosítunk, személyes műszaki támogatással – hogy Ön a vállalkozására koncentrálhasson.", + "trustBullets": [ + { + "value": "Saját dedikált infrastruktúra", + "id": "6aa4526208a0db8b7fc187a4" + }, + { + "value": "Webtárhely, domain, e-mail egy helyen", + "id": "6aa4526208a0db8b7fc187a5" + }, + { + "value": "Kis- és középvállalkozásokra szabva", + "id": "6aa4526208a0db8b7fc187a6" + } + ], + "cta": { + "primary": { + "text": "Kérjen személyre szabott konzultációt", + "href": "/kapcsolat", + "external": false + }, + "secondary": { + "text": "Webmail", + "href": "https://mail.mozdit.hu", + "external": true + } + } + }, + "about": { + "title": "Miért érdemes minket választani?", + "usps": [ + { + "id": "egyszeru", + "title": "Egyszerűen kezelhető szolgáltatások", + "description": "Nincs technoblabla. Átlátható és könnyen használható rendszereket biztosítunk, hogy Ön a saját üzletére fókuszálhasson.", + "icon": "🎯" + }, + { + "id": "nemtomeg", + "title": "Nem tömegszolgáltatás", + "description": "Egyedi figyelem és rugalmas, személyre szabott csomagok, amelyek tökéletesen igazodnak a vállalkozása igényeihez.", + "icon": "👤" + }, + { + "id": "egykezben", + "title": "Minden egy kézben", + "description": "Domain, webtárhely és üzleti e-mail szolgáltatások zökkenőmentes kezelése, mindent egyetlen megbízható helyről.", + "icon": "🧩" + }, + { + "id": "modern_tech", + "title": "Modern és Biztonságos", + "description": "TLS/SSL titkosítással, rendszeres frissítésekkel, biztonsági mentésekkel és SSD-alapú infrastruktúrával dolgozunk a szolgáltatások biztonságos és gyors működéséért.", + "icon": "🚀" + } + ] + }, + "services": { + "title": "Szolgáltatásaink", + "subtitle": "Komplex megoldásokat kínálunk, amelyek tökéletesen igazodnak a vállalkozás igényeihez.", + "items": [ + { + "id": "hosting", + "title": "Webtárhely (Hosting)", + "description": "Gyors, saját infrastruktúrán működő webtárhely rendszeres biztonsági mentéssel és folyamatos felügyelettel, hogy weboldala megbízhatóan elérhető maradjon.", + "icon": "🔥", + "features": [ + { + "value": "Éves szinten 99,9%-os rendelkezésre állási cél, tervezett karbantartások kivételével", + "id": "6aa4526208a0db8b7fc187a7" + }, + { + "value": "SSD alapú gyors kiszolgálás", + "id": "6aa4526208a0db8b7fc187a8" + }, + { + "value": "24/7 rendszeres monitoring", + "id": "6aa4526208a0db8b7fc187a9" + }, + { + "value": "Automatikus backup rendszer", + "id": "6aa4526208a0db8b7fc187aa" + } + ], + "ctaText": "Webtárhely-ajánlatot kérek" + }, + { + "id": "email", + "title": "E-mail szolgáltatás", + "description": "Saját domainhez tartozó üzleti e-mail-címek megbízható levélkezeléssel, hatékony spam- és vírusvédelemmel, webmailen vagy megszokott levelezőprogramjából.", + "icon": "📧", + "features": [ + { + "value": "Saját domain alapú e-mail címek", + "id": "6aa4526208a0db8b7fc187ab" + }, + { + "value": "Titkosított webmail- és IMAP-hozzáférés a levelek biztonságos eléréséhez", + "id": "6aa4526208a0db8b7fc187ac" + }, + { + "value": "Erős spam- és vírusvédelem", + "id": "6aa4526208a0db8b7fc187ad" + } + ], + "ctaText": "E-mail szolgáltatásról érdeklődöm" + }, + { + "id": "domain", + "title": "Domain és DNS", + "description": "Domainregisztrációt, hosszabbítást és DNS-kezelést intézünk egy helyen, hogy weboldala és levelezése megfelelően legyen beállítva.", + "icon": "🌐", + "features": [ + { + "value": "Magyar és nemzetközi domainek", + "id": "6aa4526208a0db8b7fc187ae" + }, + { + "value": "Biztonságos DNS kezelés", + "id": "6aa4526208a0db8b7fc187af" + }, + { + "value": "Levelezési DNS-beállítások: SPF, DKIM és DMARC konfiguráció a hitelesebb levélküldésért és a hamisítás elleni védelem támogatásához", + "id": "6aa4526208a0db8b7fc187b0" + }, + { + "value": "Gyors ügyintézés", + "id": "6aa4526208a0db8b7fc187b1" + } + ], + "ctaText": "Domain- és DNS-segítséget kérek" + } + ] + }, + "cta": { + "title": "Kérjen személyre szabott ajánlatot", + "subtitle": "Írja meg röviden, mire van szüksége; segítünk kiválasztani a vállalkozásához illő tárhely-, levelezési vagy domainmegoldást.", + "button": "Kérjen ajánlatot" + }, + "serviceFeatures": { + "title": "A szolgáltatás tartalmazza:" + }, + "_status": "published", + "id": "6aa20791fee88ba6840c429a" + } + }, + "about": { + "hu": { + "createdAt": "2026-09-10T01:27:45.883Z", + "updatedAt": "2026-09-11T19:11:31.026Z", + "globalType": "about", + "meta": { + "title": "Rólunk", + "description": "Ismerje meg a mozdIT Bt. történetét, küldetését és értékeit. Több mint 20 éve nyújtunk megbízható IT szolgáltatásokat.", + "ogDescription": "Ismerje meg a mozdIT Bt. történetét, küldetését és értékeit." + }, + "hero": { + "title": "Rólunk", + "subtitle": "Több mint 20 éve biztosítunk megbízható IT infrastruktúrát és személyes ügyfélszolgálatot" + }, + "story": { + "title": "Történetünk", + "paragraphs": [ + { + "value": "Kisebb weboldalak fejlesztésével és üzemeltetésével indultunk, ma már számos domain és e-mail-fiók megbízható működésének támogatásában veszünk részt. Működésünk során mindvégig fontosak maradtak számunkra alapelveink: a megbízhatóság, a személyes kapcsolat és a szakmailag igényes megoldások.", + "id": "6aa4526308a0db8b7fc187b2" + }, + { + "value": "Dedikáltan kezelt szerverinfrastruktúrán nyújtunk webtárhely-, domainkezelési és e-mail-szolgáltatásokat kis- és középvállalkozások, valamint magánszemélyek számára.", + "id": "6aa4526308a0db8b7fc187b3" + }, + { + "value": "Adatbiztonságra és megbízható működésre építünk. Partnerként közvetlen, érthető kommunikációra és személyes támogatásra számíthat: technoblabla helyett gyors, emberi válaszokra.", + "id": "6aa4526308a0db8b7fc187b4" + } + ] + }, + "mission": { + "title": "Küldetésünk és értékeink", + "subtitle": "Célunk, hogy ügyfeleink digitális jelenléte biztonságos, stabil és hatékony legyen.", + "values": [ + { + "title": "Megbízhatóság", + "description": "24/7 monitoring biztosítja szolgáltatásaink közel 100%-os rendelkezésre állását.", + "icon": "🛡️", + "id": "6aa4526308a0db8b7fc187b5" + }, + { + "title": "Személyes kapcsolat", + "description": "Személyre szabott megoldásokat kínálunk.", + "icon": "👥", + "id": "6aa4526308a0db8b7fc187b6" + }, + { + "title": "Műszaki kiválóság", + "description": "Korszerű technológiák és bevált gyakorlatok alkalmazásával biztosítjuk a legmagasabb színvonalú szolgáltatást.", + "icon": "⚡", + "id": "6aa4526308a0db8b7fc187b7" + } + ] + }, + "team": { + "title": "Szakértő csapat", + "subtitle": "Tapasztalt IT szakemberek, akik elkötelezetten és megbízhatóan dolgoznak az ügyfeleink sikeréért.", + "paragraphs": [ + { + "value": "Csapatunk tagjai több mint 20 éves tapasztalattal rendelkeznek a maguk területén, és folyamatosan követik a technológiai újdonságokat.", + "id": "6aa4526308a0db8b7fc187b8" + }, + { + "value": "Hiszünk abban, hogy a jó kommunikáció és a műszaki tudás együtt teremti meg a tökéletes ügyfélélményt.", + "id": "6aa4526308a0db8b7fc187b9" + } + ] + }, + "cta": { + "title": "Legyen Ön is elégedett ügyfelünk!", + "subtitle": "Vegye fel velünk a kapcsolatot, és beszéljük meg, hogyan segíthetünk Önnek.", + "button": "Kapcsolatfelvétel" + }, + "_status": "published", + "id": "6aa20791fee88ba6840c42a3" + }, + "en": { + "createdAt": "2026-09-10T01:27:45.883Z", + "updatedAt": "2026-09-11T19:11:31.026Z", + "globalType": "about", + "meta": { + "title": "Rólunk", + "description": "Ismerje meg a mozdIT Bt. történetét, küldetését és értékeit. Több mint 20 éve nyújtunk megbízható IT szolgáltatásokat.", + "ogDescription": "Ismerje meg a mozdIT Bt. történetét, küldetését és értékeit." + }, + "hero": { + "title": "Rólunk", + "subtitle": "Több mint 20 éve biztosítunk megbízható IT infrastruktúrát és személyes ügyfélszolgálatot" + }, + "story": { + "title": "Történetünk", + "paragraphs": [ + { + "value": "Kisebb weboldalak fejlesztésével és üzemeltetésével indultunk, ma már számos domain és e-mail-fiók megbízható működésének támogatásában veszünk részt. Működésünk során mindvégig fontosak maradtak számunkra alapelveink: a megbízhatóság, a személyes kapcsolat és a szakmailag igényes megoldások.", + "id": "6aa4526308a0db8b7fc187b2" + }, + { + "value": "Dedikáltan kezelt szerverinfrastruktúrán nyújtunk webtárhely-, domainkezelési és e-mail-szolgáltatásokat kis- és középvállalkozások, valamint magánszemélyek számára.", + "id": "6aa4526308a0db8b7fc187b3" + }, + { + "value": "Adatbiztonságra és megbízható működésre építünk. Partnerként közvetlen, érthető kommunikációra és személyes támogatásra számíthat: technoblabla helyett gyors, emberi válaszokra.", + "id": "6aa4526308a0db8b7fc187b4" + } + ] + }, + "mission": { + "title": "Küldetésünk és értékeink", + "subtitle": "Célunk, hogy ügyfeleink digitális jelenléte biztonságos, stabil és hatékony legyen.", + "values": [ + { + "title": "Megbízhatóság", + "description": "24/7 monitoring biztosítja szolgáltatásaink közel 100%-os rendelkezésre állását.", + "icon": "🛡️", + "id": "6aa4526308a0db8b7fc187b5" + }, + { + "title": "Személyes kapcsolat", + "description": "Személyre szabott megoldásokat kínálunk.", + "icon": "👥", + "id": "6aa4526308a0db8b7fc187b6" + }, + { + "title": "Műszaki kiválóság", + "description": "Korszerű technológiák és bevált gyakorlatok alkalmazásával biztosítjuk a legmagasabb színvonalú szolgáltatást.", + "icon": "⚡", + "id": "6aa4526308a0db8b7fc187b7" + } + ] + }, + "team": { + "title": "Szakértő csapat", + "subtitle": "Tapasztalt IT szakemberek, akik elkötelezetten és megbízhatóan dolgoznak az ügyfeleink sikeréért.", + "paragraphs": [ + { + "value": "Csapatunk tagjai több mint 20 éves tapasztalattal rendelkeznek a maguk területén, és folyamatosan követik a technológiai újdonságokat.", + "id": "6aa4526308a0db8b7fc187b8" + }, + { + "value": "Hiszünk abban, hogy a jó kommunikáció és a műszaki tudás együtt teremti meg a tökéletes ügyfélélményt.", + "id": "6aa4526308a0db8b7fc187b9" + } + ] + }, + "cta": { + "title": "Legyen Ön is elégedett ügyfelünk!", + "subtitle": "Vegye fel velünk a kapcsolatot, és beszéljük meg, hogyan segíthetünk Önnek.", + "button": "Kapcsolatfelvétel" + }, + "_status": "published", + "id": "6aa20791fee88ba6840c42a3" + } + }, + "services": { + "hu": { + "createdAt": "2026-09-10T01:27:45.897Z", + "updatedAt": "2026-09-11T19:11:31.046Z", + "globalType": "services", + "meta": { + "title": "Szolgáltatások", + "description": "Webhosting, email szolgáltatás és DNS adminisztráció professzionális szinten. Ismerje meg részletes szolgáltatásainkat.", + "ogDescription": "Webhosting, email szolgáltatás és DNS adminisztráció professzionális szinten." + }, + "hero": { + "title": "Szolgáltatásaink", + "subtitle": "Teljes körű IT megoldások kisvállalkozások és magánszemélyek számára" + }, + "details": { + "title": "Részletes szolgáltatásleírás", + "subtitle": "Minden szolgáltatásunk mögött évtizedes tapasztalat és modern technológia áll", + "services": [ + { + "icon": "🌐", + "title": "Web Hosting részletesen", + "description": "Weboldalak biztonságos és gyors üzemeltetése SSD tárolással, automatikus biztonsági mentéssel és 24/7 monitoringgal. Támogatjuk a PHP, Python, Node.js technológiákat és MySQL/PostgreSQL adatbázisokat.", + "specs": { + "title": "Technikai specifikációk:", + "items": [ + { + "value": "SSD tárhely 1GB-tól 100GB-ig", + "id": "6aa4526308a0db8b7fc187ba" + }, + { + "value": "Havi adatforgalom: korlátlan", + "id": "6aa4526308a0db8b7fc187bb" + }, + { + "value": "SSL tanúsítványok (Let's Encrypt vagy prémium)", + "id": "6aa4526308a0db8b7fc187bc" + }, + { + "value": "Automatikus napi biztonsági mentés", + "id": "6aa4526308a0db8b7fc187bd" + } + ] + }, + "id": "6aa4526308a0db8b7fc187be" + }, + { + "icon": "✉️", + "title": "Email szolgáltatás részletesen", + "description": "Professzionális email fiókok saját domain névvel, spam szűréssel és vírusvédelemmel. Webmail felület és IMAP/POP3/SMTP támogatás minden népszerű email klienssel.", + "specs": { + "title": "Email funkciók:", + "items": [ + { + "value": "1GB–20GB tárhelyet fiókként", + "id": "6aa4526308a0db8b7fc187bf" + }, + { + "value": "Webmail hozzáférés (Roundcube)", + "id": "6aa4526308a0db8b7fc187c0" + }, + { + "value": "Mobilalkalmazás szinkronizáció", + "id": "6aa4526308a0db8b7fc187c1" + }, + { + "value": "Spam és vírusszűrés", + "id": "6aa4526308a0db8b7fc187c2" + }, + { + "value": "Email továbbítás és automatikus válaszok", + "id": "6aa4526308a0db8b7fc187c3" + }, + { + "value": "Backup és archiválás ", + "id": "6aa4526308a0db8b7fc187c4" + } + ] + }, + "id": "6aa4526308a0db8b7fc187c5" + }, + { + "icon": "⚙️", + "title": "DNS adminisztráció részletesen", + "description": "Teljes DNS kezelés domain regisztrációval, átvitellel és professzionális beállításokkal. Gyors propagáció és megbízható névszerverek világszerte.", + "specs": { + "title": "DNS szolgáltatások:", + "items": [ + { + "value": "Domain kezelés (.hu, .com, .eu, stb.)", + "id": "6aa4526308a0db8b7fc187c6" + }, + { + "value": "Domain átvitel más szolgáltatótól", + "id": "6aa4526308a0db8b7fc187c7" + }, + { + "value": "DNS rekord kezelés (A, CNAME, MX, TXT, stb.)", + "id": "6aa4526308a0db8b7fc187c8" + }, + { + "value": "Subdomain beállítások", + "id": "6aa4526308a0db8b7fc187c9" + }, + { + "value": "Redirect és forwarding szolgáltatások", + "id": "6aa4526308a0db8b7fc187ca" + }, + { + "value": "DNSSEC támogatás", + "id": "6aa4526308a0db8b7fc187cb" + }, + { + "value": "API hozzáférés fejlesztőknek", + "id": "6aa4526308a0db8b7fc187cc" + } + ] + }, + "id": "6aa4526308a0db8b7fc187cd" + } + ] + }, + "support": { + "title": "Műszaki támogatás", + "subtitle": "Minden szolgáltatásunkhoz teljes körű műszaki támogatást biztosítunk", + "channels": [ + { + "icon": "✉️", + "title": "Email támogatás", + "description": "24 órán belüli válasz", + "id": "6aa4526308a0db8b7fc187ce" + }, + { + "icon": "📞", + "title": "Telefonos segítség", + "description": "Munkaidőben elérhető", + "id": "6aa4526308a0db8b7fc187cf" + }, + { + "icon": "🚨", + "title": "Sürgős esetek", + "description": "Azonnali beavatkozás", + "id": "6aa4526308a0db8b7fc187d0" + } + ] + }, + "cta": { + "title": "Kezdjük el a közös munkát!", + "subtitle": "Vegye fel velünk a kapcsolatot ingyenes konzultációért és egyedi ajánlatért.", + "primaryButton": "Kapcsolatfelvétel", + "secondaryButton": "Webmail belépés" + }, + "_status": "published", + "id": "6aa20791fee88ba6840c42a9" + }, + "en": { + "createdAt": "2026-09-10T01:27:45.897Z", + "updatedAt": "2026-09-11T19:11:31.046Z", + "globalType": "services", + "meta": { + "title": "Szolgáltatások", + "description": "Webhosting, email szolgáltatás és DNS adminisztráció professzionális szinten. Ismerje meg részletes szolgáltatásainkat.", + "ogDescription": "Webhosting, email szolgáltatás és DNS adminisztráció professzionális szinten." + }, + "hero": { + "title": "Szolgáltatásaink", + "subtitle": "Teljes körű IT megoldások kisvállalkozások és magánszemélyek számára" + }, + "details": { + "title": "Részletes szolgáltatásleírás", + "subtitle": "Minden szolgáltatásunk mögött évtizedes tapasztalat és modern technológia áll", + "services": [ + { + "icon": "🌐", + "title": "Web Hosting részletesen", + "description": "Weboldalak biztonságos és gyors üzemeltetése SSD tárolással, automatikus biztonsági mentéssel és 24/7 monitoringgal. Támogatjuk a PHP, Python, Node.js technológiákat és MySQL/PostgreSQL adatbázisokat.", + "specs": { + "title": "Technikai specifikációk:", + "items": [ + { + "value": "SSD tárhely 1GB-tól 100GB-ig", + "id": "6aa4526308a0db8b7fc187ba" + }, + { + "value": "Havi adatforgalom: korlátlan", + "id": "6aa4526308a0db8b7fc187bb" + }, + { + "value": "SSL tanúsítványok (Let's Encrypt vagy prémium)", + "id": "6aa4526308a0db8b7fc187bc" + }, + { + "value": "Automatikus napi biztonsági mentés", + "id": "6aa4526308a0db8b7fc187bd" + } + ] + }, + "id": "6aa4526308a0db8b7fc187be" + }, + { + "icon": "✉️", + "title": "Email szolgáltatás részletesen", + "description": "Professzionális email fiókok saját domain névvel, spam szűréssel és vírusvédelemmel. Webmail felület és IMAP/POP3/SMTP támogatás minden népszerű email klienssel.", + "specs": { + "title": "Email funkciók:", + "items": [ + { + "value": "1GB–20GB tárhelyet fiókként", + "id": "6aa4526308a0db8b7fc187bf" + }, + { + "value": "Webmail hozzáférés (Roundcube)", + "id": "6aa4526308a0db8b7fc187c0" + }, + { + "value": "Mobilalkalmazás szinkronizáció", + "id": "6aa4526308a0db8b7fc187c1" + }, + { + "value": "Spam és vírusszűrés", + "id": "6aa4526308a0db8b7fc187c2" + }, + { + "value": "Email továbbítás és automatikus válaszok", + "id": "6aa4526308a0db8b7fc187c3" + }, + { + "value": "Backup és archiválás ", + "id": "6aa4526308a0db8b7fc187c4" + } + ] + }, + "id": "6aa4526308a0db8b7fc187c5" + }, + { + "icon": "⚙️", + "title": "DNS adminisztráció részletesen", + "description": "Teljes DNS kezelés domain regisztrációval, átvitellel és professzionális beállításokkal. Gyors propagáció és megbízható névszerverek világszerte.", + "specs": { + "title": "DNS szolgáltatások:", + "items": [ + { + "value": "Domain kezelés (.hu, .com, .eu, stb.)", + "id": "6aa4526308a0db8b7fc187c6" + }, + { + "value": "Domain átvitel más szolgáltatótól", + "id": "6aa4526308a0db8b7fc187c7" + }, + { + "value": "DNS rekord kezelés (A, CNAME, MX, TXT, stb.)", + "id": "6aa4526308a0db8b7fc187c8" + }, + { + "value": "Subdomain beállítások", + "id": "6aa4526308a0db8b7fc187c9" + }, + { + "value": "Redirect és forwarding szolgáltatások", + "id": "6aa4526308a0db8b7fc187ca" + }, + { + "value": "DNSSEC támogatás", + "id": "6aa4526308a0db8b7fc187cb" + }, + { + "value": "API hozzáférés fejlesztőknek", + "id": "6aa4526308a0db8b7fc187cc" + } + ] + }, + "id": "6aa4526308a0db8b7fc187cd" + } + ] + }, + "support": { + "title": "Műszaki támogatás", + "subtitle": "Minden szolgáltatásunkhoz teljes körű műszaki támogatást biztosítunk", + "channels": [ + { + "icon": "✉️", + "title": "Email támogatás", + "description": "24 órán belüli válasz", + "id": "6aa4526308a0db8b7fc187ce" + }, + { + "icon": "📞", + "title": "Telefonos segítség", + "description": "Munkaidőben elérhető", + "id": "6aa4526308a0db8b7fc187cf" + }, + { + "icon": "🚨", + "title": "Sürgős esetek", + "description": "Azonnali beavatkozás", + "id": "6aa4526308a0db8b7fc187d0" + } + ] + }, + "cta": { + "title": "Kezdjük el a közös munkát!", + "subtitle": "Vegye fel velünk a kapcsolatot ingyenes konzultációért és egyedi ajánlatért.", + "primaryButton": "Kapcsolatfelvétel", + "secondaryButton": "Webmail belépés" + }, + "_status": "published", + "id": "6aa20791fee88ba6840c42a9" + } + }, + "contact": { + "hu": { + "createdAt": "2026-09-10T01:27:45.906Z", + "updatedAt": "2026-09-11T19:11:31.068Z", + "globalType": "contact", + "meta": { + "title": "Kapcsolat", + "description": "Vegye fel velünk a kapcsolatot! Szívesen segítünk minden IT kérdésében." + }, + "hero": { + "title": "Kapcsolat", + "subtitle": "Vegye fel velünk a kapcsolatot! Szívesen segítünk minden IT kérdésében." + }, + "form": { + "title": "Küldjön üzenetet", + "successMessage": "Köszönjük üzenetét! Hamarosan felvesszük Önnel a kapcsolatot.", + "errorMessage": "Hiba történt az üzenet küldése során. Kérjük, próbálja újra vagy írjon közvetlenül a {email} címre.", + "fields": { + "name": { + "label": "Név", + "placeholder": "Az Ön neve", + "error": "A név megadása kötelező" + }, + "email": { + "label": "Email cím", + "placeholder": "pelda@email.hu", + "errorRequired": "Az email cím megadása kötelező", + "errorInvalid": "Érvénytelen email cím formátum" + }, + "subject": { + "label": "Tárgy", + "placeholder": "Miben segíthetünk?", + "error": "A tárgy megadása kötelező" + }, + "message": { + "label": "Üzenet", + "placeholder": "Írja le részletesen kérését vagy kérdését...", + "errorRequired": "Az üzenet megadása kötelező", + "errorMinLength": "Az üzenet legalább 10 karakter hosszú legyen" + }, + "gdpr": { + "label": "Elfogadom az adatkezelési tájékoztatót és hozzájárulok személyes adataim kezeléséhez a kapcsolatfelvétel céljából.", + "error": "Az adatkezelési tájékoztató elfogadása kötelező" + } + }, + "submitButton": "Üzenet küldése", + "submittingButton": "Küldés..." + }, + "info": { + "title": "Elérhetőségek", + "email": { + "title": "Email", + "responseTime": "24 órán belül válaszolunk" + }, + "company": { + "title": "Cég" + }, + "webmail": { + "title": "Webmail hozzáférés", + "linkText": "Webmail belépés →", + "subtitle": "Ügyfeleink számára" + } + }, + "faq": { + "title": "Gyakori kérdések", + "items": [ + { + "question": "Milyen gyorsan válaszolnak?", + "answer": "Email üzenetekre 24 órán belül, sürgős esetekben telefonon is elérhetők vagyunk.", + "id": "6aa4526308a0db8b7fc187d1" + }, + { + "question": "Van ingyenes konzultáció?", + "answer": "Igen! Az első konzultáció mindig ingyenes, hogy megismerjük az Ön igényeit.", + "id": "6aa4526308a0db8b7fc187d2" + }, + { + "question": "Milyen fizetési módokat fogadnak el?", + "answer": "Banki átutalás, Azonnali Fizetés, Qvik", + "id": "6aa4526308a0db8b7fc187d3" + } + ] + }, + "_status": "published", + "id": "6aa20791fee88ba6840c42ae" + }, + "en": { + "createdAt": "2026-09-10T01:27:45.906Z", + "updatedAt": "2026-09-11T19:11:31.068Z", + "globalType": "contact", + "meta": { + "title": "Kapcsolat", + "description": "Vegye fel velünk a kapcsolatot! Szívesen segítünk minden IT kérdésében." + }, + "hero": { + "title": "Kapcsolat", + "subtitle": "Vegye fel velünk a kapcsolatot! Szívesen segítünk minden IT kérdésében." + }, + "form": { + "title": "Küldjön üzenetet", + "successMessage": "Köszönjük üzenetét! Hamarosan felvesszük Önnel a kapcsolatot.", + "errorMessage": "Hiba történt az üzenet küldése során. Kérjük, próbálja újra vagy írjon közvetlenül a {email} címre.", + "fields": { + "name": { + "label": "Név", + "placeholder": "Az Ön neve", + "error": "A név megadása kötelező" + }, + "email": { + "label": "Email cím", + "placeholder": "pelda@email.hu", + "errorRequired": "Az email cím megadása kötelező", + "errorInvalid": "Érvénytelen email cím formátum" + }, + "subject": { + "label": "Tárgy", + "placeholder": "Miben segíthetünk?", + "error": "A tárgy megadása kötelező" + }, + "message": { + "label": "Üzenet", + "placeholder": "Írja le részletesen kérését vagy kérdését...", + "errorRequired": "Az üzenet megadása kötelező", + "errorMinLength": "Az üzenet legalább 10 karakter hosszú legyen" + }, + "gdpr": { + "label": "Elfogadom az adatkezelési tájékoztatót és hozzájárulok személyes adataim kezeléséhez a kapcsolatfelvétel céljából.", + "error": "Az adatkezelési tájékoztató elfogadása kötelező" + } + }, + "submitButton": "Üzenet küldése", + "submittingButton": "Küldés..." + }, + "info": { + "title": "Elérhetőségek", + "email": { + "title": "Email", + "responseTime": "24 órán belül válaszolunk" + }, + "company": { + "title": "Cég" + }, + "webmail": { + "title": "Webmail hozzáférés", + "linkText": "Webmail belépés →", + "subtitle": "Ügyfeleink számára" + } + }, + "faq": { + "title": "Gyakori kérdések", + "items": [ + { + "question": "Milyen gyorsan válaszolnak?", + "answer": "Email üzenetekre 24 órán belül, sürgős esetekben telefonon is elérhetők vagyunk.", + "id": "6aa4526308a0db8b7fc187d1" + }, + { + "question": "Van ingyenes konzultáció?", + "answer": "Igen! Az első konzultáció mindig ingyenes, hogy megismerjük az Ön igényeit.", + "id": "6aa4526308a0db8b7fc187d2" + }, + { + "question": "Milyen fizetési módokat fogadnak el?", + "answer": "Banki átutalás, Azonnali Fizetés, Qvik", + "id": "6aa4526308a0db8b7fc187d3" + } + ] + }, + "_status": "published", + "id": "6aa20791fee88ba6840c42ae" + } + }, + "common": { + "hu": { + "createdAt": "2026-09-10T01:27:45.913Z", + "updatedAt": "2026-09-11T19:11:31.086Z", + "globalType": "common", + "buttons": { + "contact": "Kapcsolatfelvétel", + "learnMore": "További információk", + "webmail": "Webmail belépés", + "sendMessage": "Üzenet küldése" + }, + "labels": { + "required": "*", + "features": "Szolgáltatás jellemzők:" + }, + "validation": { + "required": "Ez a mező kötelező", + "invalidEmail": "Érvénytelen email cím formátum", + "minLength": "Legalább {min} karakter szükséges" + }, + "staging": { + "banner": "⚠ STAGING / TESZTKÖRNYEZET — A STAGING OLDALT LÁTOD" + }, + "footer": { + "copyright": "© 2002–{year} mozdIT Bt. Minden jog fenntartva.", + "address": "Szigetszentmiklós, Magyarország" + }, + "a11y": { + "openMenu": "Főmenü megnyitása", + "closeMenu": "Főmenü bezárása" + }, + "_status": "published", + "id": "6aa20791fee88ba6840c42b3" + }, + "en": { + "createdAt": "2026-09-10T01:27:45.913Z", + "updatedAt": "2026-09-11T19:11:31.086Z", + "globalType": "common", + "buttons": { + "contact": "Contact", + "learnMore": "Learn more", + "webmail": "Webmail", + "sendMessage": "Send message" + }, + "labels": { + "required": "*", + "features": "Szolgáltatás jellemzők:" + }, + "validation": { + "required": "Ez a mező kötelező", + "invalidEmail": "Érvénytelen email cím formátum", + "minLength": "Legalább {min} karakter szükséges" + }, + "staging": { + "banner": "⚠ STAGING / TESZTKÖRNYEZET — A STAGING OLDALT LÁTOD" + }, + "footer": { + "copyright": "© 2002–{year} mozdIT Bt. Minden jog fenntartva.", + "address": "Szigetszentmiklós, Magyarország" + }, + "a11y": { + "openMenu": "Főmenü megnyitása", + "closeMenu": "Főmenü bezárása" + }, + "_status": "published", + "id": "6aa20791fee88ba6840c42b3" + } + } + }, + "collections": { + "legalPages": { + "hu": [ + { + "createdAt": "2026-09-10T10:12:21.446Z", + "updatedAt": "2026-09-11T19:11:31.121Z", + "slug": "hasznalati-feltetelek", + "title": "Használati Feltételek (ÁSZF)", + "lastUpdated": "2026. augusztus 22.", + "sections": [ + { + "id": "bevezetes", + "title": "1. Általános rendelkezések", + "content": "Jelen Általános Szerződési Feltételek (ÁSZF) a mozdIT Bt. (székhely: Szigetszentmiklós, Magyarország; e-mail: info@mozdit.hu) által nyújtott webtárhely, e-mail szolgáltatás és domain regisztráció szolgáltatásainak igénybevételére vonatkozó szabályokat tartalmazzák. A szolgáltatások megrendelésével és használatával az Ügyfél elfogadja jelen feltételeket." + }, + { + "id": "szolgaltatasok", + "title": "2. Szolgáltatások leírása", + "content": "A mozdIT Bt. dedikált szerverein nyújt webtárhely, e-mail fiók és DNS szolgáltatásokat. A szolgáltatások garantált rendelkezésre állása 99,9% éves szinten. A szolgáltató automatikus biztonsági mentéseket végez, de javasolja az Ügyfél számára is az adatai rendszeres mentését." + }, + { + "id": "ugyfel-kotelezettsegei", + "title": "3. Az Ügyfél kötelezettségei", + "content": "Az Ügyfél köteles a szolgáltatásokat rendeltetésszerűen használni. Szigorúan tilos a tárhelyen és e-mail fiókokban illegális, szerzői jogot sértő tartalmak elhelyezése, valamint spam (kéretlen reklámlevél) tömeges küldése. A Szolgáltató fenntartja a jogot, hogy jogsértés vagy hálózatot veszélyeztető tevékenység (pl. DDoS forrás) észlelése esetén a szolgáltatást előzetes értesítés nélkül azonnal szüneteltesse vagy megszüntesse." + }, + { + "id": "dijazas", + "title": "4. Díjazás és fizetési feltételek", + "content": "A szolgáltatások díjai a felek által megkötött egyedi megállapodás vagy elfogadott árajánlat alapján fizetendők. A számlázás elektronikus úton történik. A fizetési határidő elmulasztása esetén a Szolgáltató jogosult a szolgáltatás szüneteltetésére, melyről az Ügyfelet előzetesen e-mailben értesíti." + }, + { + "id": "felelosseg", + "title": "5. Felelősségkorlátozás", + "content": "A Szolgáltató mindent megtesz az adatok biztonsága és a folyamatos üzemeltetés érdekében, ugyanakkor nem vállal felelősséget a vis maior, illetve a harmadik felek által okozott (pl. hálózati) kimaradások miatti esetleges adatvesztésért vagy elmaradt haszonért." + } + ], + "_status": "published", + "id": "6aa28285a1be18b9875f67e2" + }, + { + "createdAt": "2026-09-10T10:12:21.438Z", + "updatedAt": "2026-09-11T19:11:31.106Z", + "slug": "adatvedelem", + "title": "Adatvédelmi Tájékoztató", + "lastUpdated": "2026. augusztus 22.", + "sections": [ + { + "id": "bevezetes", + "title": "1. Bevezetés", + "content": "A mozdIT Bt. (székhely: Szigetszentmiklós Vihar utca 13., Magyarország; e-mail: info@mozdit.hu) mint adatkezelő elkötelezett a felhasználók személyes adatainak védelme iránt. Jelen tájékoztató célja, hogy az Európai Unió Általános Adatvédelmi Rendelete (GDPR) előírásainak megfelelően átlátható módon bemutassa cégünk adatkezelési gyakorlatát." + }, + { + "id": "adatkezeles-celja", + "title": "2. Milyen adatokat kezelünk, mi a céljuk és a jogalapjuk?", + "content": "A szolgáltatásaink nyújtása során az alábbi adatokat kezeljük:\n\n• **Kapcsolatfelvétel és űrlapok:** Név, e-mail cím, telefonszám, a beküldött üzenet tartalma. (Cél: ügyfélkapcsolat, árajánlatadás. Jogalap: hozzájárulás és szerződést megelőző lépések.)\n• **Ügyfél-nyilvántartás és számlázás:** Számlázási név és cím, adószám, kapcsolattartó adatai. (Cél: szolgáltatás biztosítása, számlázás. Jogalap: jogi kötelezettség, szerződés teljesítése.)\n• **Webtárhely és e-mail szolgáltatás:** Technikai naplófájlok (IP címek, belépési időpontok), e-mail forgalmi adatok szűrése (spam, vírusok). (Cél: IT biztonság fenntartása, szolgáltatás biztosítása. Jogalap: szerződés teljesítése, jogos érdek.)\n• **Domain-regisztráció:** Tulajdonosi és kapcsolattartói adatok. (Cél: domain delegáció ügyintézése a regisztrátorok felé. Jogalap: szerződés teljesítése.)\n• **Sütik (Cookies):** Weboldalunk a megfelelő technikai működés érdekében alapvető (munkamenet) sütiket használ. Harmadik fél analitikai kódjait (pl. Meta pixel) jelenleg nem használjuk az alapvető nyomkövetéshez." + }, + { + "id": "adattovabbitas", + "title": "3. Adattovábbítás és adatfeldolgozók", + "content": "A személyes adatokat bizalmasan kezeljük, és alapvetően a mozdIT Bt. saját, dedikált infrastruktúráján tároljuk. Harmadik félnek csak jogszabályi kötelezettség alapján (pl. NAV részére könyvelési okokból), illetve a domain-regisztrációs szolgáltatások keretében továbbítunk adatokat az illetékes regisztrátorok (pl. ISZT) felé." + }, + { + "id": "adatmegorzes", + "title": "4. Az adatkezelés időtartama", + "content": "A számviteli bizonylatokat a Számviteli törvény értelmében 8 évig megőrizzük. A kapcsolatfelvételi űrlapon megadott, de szerződéssel nem végződő adatokat az ügy lezárását követő 1 év elteltével töröljük. A webszerverek technikai naplófájljait (logokat) a biztonsági és üzemeltetési feladatok ellátása céljából legfeljebb 30 napig őrizzük meg." + }, + { + "id": "jogok", + "title": "5. Az érintettek jogai", + "content": "Az érintettek bármikor tájékoztatást kérhetnek adataik kezeléséről, kérhetik azok helyesbítését, törlését (a kötelező adatkezelések kivételével) vagy zárolását. Kérésüket az info@mozdit.hu e-mail címre küldhetik el, amire indokolatlan késedelem nélkül, de legkésőbb egy hónapon belül válaszolunk. Jogosulatlan adatkezelés esetén a Nemzeti Adatvédelmi és Információszabadság Hatósághoz (NAIH) lehet fordulni panasszal." + } + ], + "_status": "published", + "id": "6aa28285a1be18b9875f67de" + } + ], + "en": [ + { + "createdAt": "2026-09-10T10:12:21.446Z", + "updatedAt": "2026-09-11T19:11:31.121Z", + "slug": "hasznalati-feltetelek", + "title": "Használati Feltételek (ÁSZF)", + "lastUpdated": "2026. augusztus 22.", + "sections": [ + { + "id": "bevezetes", + "title": "1. Általános rendelkezések", + "content": "Jelen Általános Szerződési Feltételek (ÁSZF) a mozdIT Bt. (székhely: Szigetszentmiklós, Magyarország; e-mail: info@mozdit.hu) által nyújtott webtárhely, e-mail szolgáltatás és domain regisztráció szolgáltatásainak igénybevételére vonatkozó szabályokat tartalmazzák. A szolgáltatások megrendelésével és használatával az Ügyfél elfogadja jelen feltételeket." + }, + { + "id": "szolgaltatasok", + "title": "2. Szolgáltatások leírása", + "content": "A mozdIT Bt. dedikált szerverein nyújt webtárhely, e-mail fiók és DNS szolgáltatásokat. A szolgáltatások garantált rendelkezésre állása 99,9% éves szinten. A szolgáltató automatikus biztonsági mentéseket végez, de javasolja az Ügyfél számára is az adatai rendszeres mentését." + }, + { + "id": "ugyfel-kotelezettsegei", + "title": "3. Az Ügyfél kötelezettségei", + "content": "Az Ügyfél köteles a szolgáltatásokat rendeltetésszerűen használni. Szigorúan tilos a tárhelyen és e-mail fiókokban illegális, szerzői jogot sértő tartalmak elhelyezése, valamint spam (kéretlen reklámlevél) tömeges küldése. A Szolgáltató fenntartja a jogot, hogy jogsértés vagy hálózatot veszélyeztető tevékenység (pl. DDoS forrás) észlelése esetén a szolgáltatást előzetes értesítés nélkül azonnal szüneteltesse vagy megszüntesse." + }, + { + "id": "dijazas", + "title": "4. Díjazás és fizetési feltételek", + "content": "A szolgáltatások díjai a felek által megkötött egyedi megállapodás vagy elfogadott árajánlat alapján fizetendők. A számlázás elektronikus úton történik. A fizetési határidő elmulasztása esetén a Szolgáltató jogosult a szolgáltatás szüneteltetésére, melyről az Ügyfelet előzetesen e-mailben értesíti." + }, + { + "id": "felelosseg", + "title": "5. Felelősségkorlátozás", + "content": "A Szolgáltató mindent megtesz az adatok biztonsága és a folyamatos üzemeltetés érdekében, ugyanakkor nem vállal felelősséget a vis maior, illetve a harmadik felek által okozott (pl. hálózati) kimaradások miatti esetleges adatvesztésért vagy elmaradt haszonért." + } + ], + "_status": "published", + "id": "6aa28285a1be18b9875f67e2" + }, + { + "createdAt": "2026-09-10T10:12:21.438Z", + "updatedAt": "2026-09-11T19:11:31.106Z", + "slug": "adatvedelem", + "title": "Adatvédelmi Tájékoztató", + "lastUpdated": "2026. augusztus 22.", + "sections": [ + { + "id": "bevezetes", + "title": "1. Bevezetés", + "content": "A mozdIT Bt. (székhely: Szigetszentmiklós Vihar utca 13., Magyarország; e-mail: info@mozdit.hu) mint adatkezelő elkötelezett a felhasználók személyes adatainak védelme iránt. Jelen tájékoztató célja, hogy az Európai Unió Általános Adatvédelmi Rendelete (GDPR) előírásainak megfelelően átlátható módon bemutassa cégünk adatkezelési gyakorlatát." + }, + { + "id": "adatkezeles-celja", + "title": "2. Milyen adatokat kezelünk, mi a céljuk és a jogalapjuk?", + "content": "A szolgáltatásaink nyújtása során az alábbi adatokat kezeljük:\n\n• **Kapcsolatfelvétel és űrlapok:** Név, e-mail cím, telefonszám, a beküldött üzenet tartalma. (Cél: ügyfélkapcsolat, árajánlatadás. Jogalap: hozzájárulás és szerződést megelőző lépések.)\n• **Ügyfél-nyilvántartás és számlázás:** Számlázási név és cím, adószám, kapcsolattartó adatai. (Cél: szolgáltatás biztosítása, számlázás. Jogalap: jogi kötelezettség, szerződés teljesítése.)\n• **Webtárhely és e-mail szolgáltatás:** Technikai naplófájlok (IP címek, belépési időpontok), e-mail forgalmi adatok szűrése (spam, vírusok). (Cél: IT biztonság fenntartása, szolgáltatás biztosítása. Jogalap: szerződés teljesítése, jogos érdek.)\n• **Domain-regisztráció:** Tulajdonosi és kapcsolattartói adatok. (Cél: domain delegáció ügyintézése a regisztrátorok felé. Jogalap: szerződés teljesítése.)\n• **Sütik (Cookies):** Weboldalunk a megfelelő technikai működés érdekében alapvető (munkamenet) sütiket használ. Harmadik fél analitikai kódjait (pl. Meta pixel) jelenleg nem használjuk az alapvető nyomkövetéshez." + }, + { + "id": "adattovabbitas", + "title": "3. Adattovábbítás és adatfeldolgozók", + "content": "A személyes adatokat bizalmasan kezeljük, és alapvetően a mozdIT Bt. saját, dedikált infrastruktúráján tároljuk. Harmadik félnek csak jogszabályi kötelezettség alapján (pl. NAV részére könyvelési okokból), illetve a domain-regisztrációs szolgáltatások keretében továbbítunk adatokat az illetékes regisztrátorok (pl. ISZT) felé." + }, + { + "id": "adatmegorzes", + "title": "4. Az adatkezelés időtartama", + "content": "A számviteli bizonylatokat a Számviteli törvény értelmében 8 évig megőrizzük. A kapcsolatfelvételi űrlapon megadott, de szerződéssel nem végződő adatokat az ügy lezárását követő 1 év elteltével töröljük. A webszerverek technikai naplófájljait (logokat) a biztonsági és üzemeltetési feladatok ellátása céljából legfeljebb 30 napig őrizzük meg." + }, + { + "id": "jogok", + "title": "5. Az érintettek jogai", + "content": "Az érintettek bármikor tájékoztatást kérhetnek adataik kezeléséről, kérhetik azok helyesbítését, törlését (a kötelező adatkezelések kivételével) vagy zárolását. Kérésüket az info@mozdit.hu e-mail címre küldhetik el, amire indokolatlan késedelem nélkül, de legkésőbb egy hónapon belül válaszolunk. Jogosulatlan adatkezelés esetén a Nemzeti Adatvédelmi és Információszabadság Hatósághoz (NAIH) lehet fordulni panasszal." + } + ], + "_status": "published", + "id": "6aa28285a1be18b9875f67de" + } + ] + }, + "partners": { + "hu": [ + { + "createdAt": "2026-09-10T10:30:29.627Z", + "updatedAt": "2026-09-11T19:11:31.141Z", + "name": "Angele Pihenőház", + "url": "https://angelepihenohaz.hu", + "logo": { + "createdAt": "2026-09-10T10:30:29.621Z", + "updatedAt": "2026-09-10T10:30:29.621Z", + "alt": "Angele Pihenőház", + "url": "/api/media/file/angele-pihenohaz.png", + "filename": "angele-pihenohaz.png", + "mimeType": "image/png", + "filesize": 12289, + "width": 270, + "height": 80, + "focalX": 50, + "focalY": 50, + "id": "6aa286c594549e0a0605bea2", + "thumbnailURL": null + }, + "_status": "published", + "id": "6aa286c594549e0a0605bea6" + } + ], + "en": [ + { + "createdAt": "2026-09-10T10:30:29.627Z", + "updatedAt": "2026-09-11T19:11:31.141Z", + "name": "Angele Pihenőház", + "url": "https://angelepihenohaz.hu", + "logo": { + "createdAt": "2026-09-10T10:30:29.621Z", + "updatedAt": "2026-09-10T10:30:29.621Z", + "alt": "Angele Pihenőház", + "url": "/api/media/file/angele-pihenohaz.png", + "filename": "angele-pihenohaz.png", + "mimeType": "image/png", + "filesize": 12289, + "width": 270, + "height": 80, + "focalX": 50, + "focalY": 50, + "id": "6aa286c594549e0a0605bea2", + "thumbnailURL": null + }, + "_status": "published", + "id": "6aa286c594549e0a0605bea6" + } + ] + } + } +} \ No newline at end of file diff --git a/docs/felhasznaloi-utmutato.md b/docs/felhasznaloi-utmutato.md deleted file mode 100644 index 3b851d2..0000000 --- a/docs/felhasznaloi-utmutato.md +++ /dev/null @@ -1,122 +0,0 @@ -# mozdIT — Felhasználói útmutató - -Ez az útmutató a mozdIT weboldalt és a hozzá tartozó **Content Editor** (CMS) felületet írja le nem műszaki felhasználóknak. - -A dokumentum a repó része, és **folyamatosan karbantartott**: minden funkcióváltozásnál a fejlesztő frissíti. A CMS ❓ Súgó menüpontja ezt a fájlt jeleníti meg. - ---- - -## 1. A weboldal - -### Hol érhető el? - -- **Staging (teszt) oldal**: [https://stage.mozdit.hu](https://stage.mozdit.hu) — itt ellenőrizhetők a friss változtatások éles környezetben, még a véglegesítés előtt. -- A staging oldal tetején **sárga figyelmeztető sáv** jelzi, hogy tesztkörnyezetet látsz. - -### Oldalak - -- **Kezdőlap** — `https://stage.mozdit.hu/` -- **Rólunk** — `/rolunk` -- **Szolgáltatások** — `/szolgaltatasok` -- **Kapcsolat** — `/kapcsolat` (űrlap, ami beérkező üzenetként tárolódik) -- **Adatvédelmi tájékoztató** — `/adatvedelem` -- **Felhasználási feltételek** — `/felhasznalasi-feltetelek` - -### Hogyan változik a weboldal tartalma? - -1. A szerkesztő a **Content Editorban** módosítja a szövegeket (2. fejezet). -2. **💾 Mentés** — a módosítás elmentődik, azonnali biztonsági mentéssel. -3. **🚀 Publikálás** — a változtatás bekerül a Git repóba, és automatikusan deployol a staging oldalra. -4. Az éles (production) weboldalra a tartalom csak ellenőrzött, szándékos deploy lépéssel kerül fel — a CMS-ből soha nem publisholódik automatikusan productionre. - ---- - -## 2. Content Editor (CMS) - -### Belépés és kilépés - -- A CMS a kiadott címen érhető el (staging: `https://cms.stage.llmdev.mozdit.hu`). -- **Bejelentkezés**: a logós bejelentkező oldalon add meg a **felhasználónevet és jelszót** (ezt az adminisztrátor adja), majd kattints a Belépés gombra. -- Több **sikertelen próbálkozás** (5) után a belépés kb. 15 percre zárolásra kerül. -- A belépés **8 óráig érvényes** — ezután a CMS visszairányít a bejelentkező oldalra, ahol újra meg kell adni a jelszót. -- **🚪 Kilépés**: az alsó sáv gombja — egy megerősítő kérdés („Biztosan ki szeretnél lépni?") után kijelentkezel, és megjelenik a bejelentkező oldal. - -### Felület áttekintés - -- **Fájl fülek** (felül): oldalankénti tartalom — Kezdőlap, Rólunk, Szolgáltatások, Kapcsolat, jogi oldalak, közös szövegek. -- **Szerkesztőfelület**: a kiválasztott oldal összes szerkeszthető mezője. -- **Alsó sáv**: 💾 Mentés, 🚀 Publikálás, 🔗 Előnézet, ❓ Súgó, 🚪 Kilépés, valamint a **futó verzió** (pl. `va7b1a2c`) — ha a fejlesztő megkér, hogy ellenőrizd a verziót, ezt a jelölést mondd neki. - -### Szöveg szerkesztése - -- A mezők fölötti **útvonal** (pl. `hero.title`) jelzi, hol jelenik meg a szöveg az oldalon. -- Mezőtípusok: - - **Egysoros / több soros szövegmező** — általános szöveg; a hosszabb szöveg automatikusan nagyobb mezőben szerkeszthető. - - **Jelölőnégyzet** — be/ki (igen/nem) érték. - - **Számmező** — numerikus érték. -- A módosítás **nem kerül azonnal az oldalra** — ahhoz Mentés, majd Publikálás kell. - -### Listák szerkesztése - -- Lista elem (pl. egy jelszó, egy szolgáltatás tulajdonság): **❌ gombbal törölhető**. -- **➕ Új elem hozzáadása** gomb: új elem beszúrása a lista végére (üres, a meglévőkhöz hasonló űrlappal). -- Kártyás listáknál (pl. szolgáltatások) minden kártya külön törölhető a kártya alján lévő gombbal. - -### 🎨 Logó kezelése - -- Az alsó sáv **🎨 Logó** gombja megnyitja a logókezelő oldalt. -- Két logó cserélhető: a **weboldal fejléclogója** (szöveges) és a **CMS bejelentkező oldal ikonja**. -- **Partner logó feltöltése**: ugyanitt — a fájlnév megadása után a feltöltött PNG a `/partners/…` elérési útra kerül; ezt az utat másold a partner **logo** mezőjébe. -- Csak **PNG**, max. **1 MB**; ajánlott átlátszó háttér a sötét fejléchez. -- A régi logó mentésre kerül — a csere biztonságos és visszavonható (a mentések a `.content-backups` mappában). -- A **CMS azonnal** az új logót mutatja; a **weboldalon a Publikálás (deploy) után** jelenik meg. - -### 🤝 Partnerek - -- A kezdőlap **„Partnereink"** szekciója a Szolgáltatások alatt jelenik meg (logó + név + hivatkozás). -- A **Kezdőlap** fülön a `partners.items` listában szerkeszthetők: `name` (név), `url` (hivatkozás), `logo` (elérési út, pl. `/partners/acme.png`). -- Új logó: a **🎨 Logó** oldalon töltsd fel, a visszaadott utat illeszd a `logo` mezőbe. - -### ⌨️ Gyorsbillentyűk - -- **Ctrl/Cmd + S** — Mentés -- **Ctrl/Cmd + P** — Publikálás -- **Ctrl/Cmd + Shift + V** — Verziók panel megnyitása új fülön -- **?** — gyorsbillentyű-súgó megjelenítése (Esc vagy kattintás zárja) - -A gyorsbillentyűk csak a szerkesztő főoldalán működnek; beviteli mezőben gépelve a normál karakterként viselkednek. - -### 🕘 Verziók — korábbi állapotok - -- Az alsó sáv **🕘 Verziók** gombja megnyitja az éppen szerkesztett fájl mentéseit (minden Mentés automatikus másolatot készít). -- **⚖ Összehasonlítás**: megmutatja, mi változott az adott mentéshez képest (piros = a mentésben lévő régi szöveg, zöld = a jelenlegi). -- **↩ Visszaállítás**: egy kattintással visszaállítja a mentést. A visszaállítás **előtt a jelenlegi tartalom is mentésre kerül**, tehát a visszaállítás is visszavonható. -- A visszaállítás sémaillesztésen megy át — hibás mentést nem lehet visszaállítani. -- Visszaállítás után a nyitott szerkesztő fülek frissítést kérnek (a tartalom megváltozott). - -### 💾 Mentés - -- A Mentés **ellenőrzi a tartalmat**: hiányzó vagy rossz típusú mező esetén hibaüzenetet kapsz, és a mentés nem történik meg — az oldal így nem tud elromlani. -- **Ha a tartalom megváltozott, mióta a lapot megnyitottad** (pl. közben deploy történt vagy egy másik fülben mentett valaki), a Mentés figyelmeztet: ilyenkor döntsd el, hogy frissíted a lapot az új tartalomra (a szerkesztésed elvész), vagy megszakítod. Ezzel a védelemmel nem írható véletlenül felül senki módosítása. -- Minden sikeres mentés **biztonsági mentést** készít a szerveren (`.content-backups/`), és naplózza a műveletet. -- Ha a Mentés sikeres, a mentett állapotot **Előnézet** gombbal nézheted meg a staging oldalon. - -### 🚀 Publikálás - -- A Publikálás **commitolja és feltolja** a változtatásokat, majd elindítja a staging deployt. -- „Nincs új változtatás." üzenet: nincs új mentett változtatás — ez **nem hiba**, ilyenkor deploy sem indul. -- A publikálás korlátozva van (3 próbálkozás / 15 perc) a véletlen tömeges deploy elkerülésére. -- A deploy eltarthat 1-2 percig; az eredményt az Előnézet gombbal ellenőrizheted. - -### Biztonság - -- Több **sikertelen belépési kísérlet** után a rendszer átmenetileg letiltja a belépést a gépedről (kb. 15 percre). -- Minden mentés és publikálás **naplózva** van (audit log) a nyomonkövethetőség érdekében. - ---- - -## Karbantartás (fejlesztőknek) - -- Forrás: `docs/felhasznaloi-utmutato.md` — a CMS a `/guide` útvonalon rendereli ki. -- **Szabály**: minden CMS- vagy honlapfunkció változásnál frissítsd ezt a fájlt ugyanabban a commitban. -- Az útmutató támogatott formátuma: címsorok, **félkövér**, `kód`, listák, linkek, elválasztó vonalak. diff --git a/proto/scripts/export-content-snapshot.ts b/proto/scripts/export-content-snapshot.ts new file mode 100644 index 0000000..1de6054 --- /dev/null +++ b/proto/scripts/export-content-snapshot.ts @@ -0,0 +1,70 @@ +/** + * MITHOME-93 biztonsági lépés: a régi egyedi CMS (content-editor.js + + * scripts/cms-*.js) eltávolítása előtt exportálja a Payload jelenlegi + * (publikált) szöveges tartalmát egy olvasható JSON fájlba — mindkét + * locale-lal (hu, en) —, hogy git-committolt, ember által is átnézhető + * biztonsági mentés maradjon a leépítés pillanatáról. + * + * NEM helyettesíti a git history-t (a src/content/*.json fájlok minden + * korábbi szerkesztése megvan commit-onként), és nem helyettesíti a + * MongoDB-t (az az élő forrás) — ez egy plusz, könnyen olvasható + * pillanatkép a "mielőtt törlünk, mentsünk" elv jegyében. + * + * Futtatás (proto/ mappából, futó MongoDB-vel és beállított env-ekkel): + * node --env-file=.env.local --import tsx scripts/export-content-snapshot.ts + */ +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' +import { getPayload, type Payload } from 'payload' +import config from '../src/payload.config' + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)) +const outDir = path.resolve(scriptDir, '../../docs/backups') + +const GLOBAL_SLUGS = ['home', 'about', 'services', 'contact', 'common'] as const +const LOCALES = ['hu', 'en'] as const + +async function exportGlobals(payload: Payload) { + const result: Record> = {} + for (const slug of GLOBAL_SLUGS) { + result[slug] = {} + for (const locale of LOCALES) { + result[slug][locale] = await payload.findGlobal({ slug, locale }) + } + } + return result +} + +async function exportCollection(payload: Payload, collection: 'legal-pages' | 'partners') { + const result: Record = {} + for (const locale of LOCALES) { + const { docs } = await payload.find({ collection, locale, limit: 1000 }) + result[locale] = docs + } + return result +} + +async function run() { + const payload = await getPayload({ config }) + + const snapshot = { + exportedAt: new Date().toISOString(), + reason: 'MITHOME-93 — régi CMS (content-editor.js) leépítése előtti biztonsági mentés', + globals: await exportGlobals(payload), + collections: { + legalPages: await exportCollection(payload, 'legal-pages'), + partners: await exportCollection(payload, 'partners'), + }, + } + + fs.mkdirSync(outDir, { recursive: true }) + const filename = `payload-content-snapshot-${snapshot.exportedAt.replace(/[:.]/g, '-')}.json` + const outPath = path.join(outDir, filename) + fs.writeFileSync(outPath, JSON.stringify(snapshot, null, 2), 'utf8') + + payload.logger.info(`Snapshot kiírva: ${outPath}`) + process.exit(0) +} + +run() diff --git a/proto/src/__tests__/cms-editor-client.test.ts b/proto/src/__tests__/cms-editor-client.test.ts deleted file mode 100644 index 21748e7..0000000 --- a/proto/src/__tests__/cms-editor-client.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Regression test for the Content Editor browser script: deleting an array - * item via its ❌ button must reindex the remaining items, otherwise collect() - * produces sparse arrays (null holes) that fail schema validation - * ("$.details.services[1].specs.items[0]: string érték szükséges"). - * - * Runs the REAL scripts/cms-editor-client.js in jsdom and clicks the actual - * delete buttons — earlier coverage only exercised reindexItems() directly, - * which missed that the onclick handler removed the node BEFORE looking up - * its container (detached node → closest() === null → no reindex). - */ -import fs from 'fs' -import path from 'path' - -const clientJs = fs.readFileSync(path.join(__dirname, '../../../scripts/cms-editor-client.js'), 'utf8') - -const service = (n: number) => ({ - id: `svc-${n}`, - title: `Szolgáltatás ${n}`, - description: `Leírás ${n}`, - icon: '🔧', - features: [`feature ${n}`], - ctaText: 'CTA', -}) - -const makeData = () => ({ - details: { - title: 'Részletek', - subtitle: 'Alcím', - services: [ - { icon: 'a', title: 's0', description: 'd0', specs: { title: 't0', items: ['a0', 'b0', 'c0'] } }, - { icon: 'b', title: 's1', description: 'd1', specs: { title: 't1', items: ['a1', 'b1', 'c1'] } }, - { icon: 'c', title: 's2', description: 'd2', specs: { title: 't2', items: ['a2', 'b2', 'c2'] } }, - ], - }, -}) - -function bootClient(data: unknown) { - ;(global as any).DATA = data - ;(global as any).FILE = 'services' - ;(global as any).CSRF_TOKEN = 'test-token' - ;(global as any).fetch = jest.fn() - document.body.innerHTML = '
' - // sloppy-mode eval publishes the script's functions on the global object - ;(0, eval)(clientJs) -} - -function deleteButtonFor(dataPath: string): HTMLButtonElement { - const field = document.querySelector(`[data-path="${CSS.escape(dataPath)}"]`) as HTMLElement - expect(field).not.toBeNull() - const wrap = field.closest('.str-item') as HTMLElement - expect(wrap).not.toBeNull() - return wrap.querySelector('.btn-del') as HTMLButtonElement -} - -afterEach(() => { - delete (global as any).DATA - delete (global as any).FILE - delete (global as any).CSRF_TOKEN -}) - -describe('Content Editor client delete/reindex', () => { - it('deleting a nested string array item keeps the remaining items dense', () => { - const data = makeData() - bootClient(data) - - deleteButtonFor('details.services[1].specs.items[0]').click() - - const collected = (global as any).collect() - expect(collected.details.services[1].specs.items).toEqual(['b1', 'c1']) - expect(collected.details.services[0].specs.items).toEqual(['a0', 'b0', 'c0']) - expect(collected.details.services[2].specs.items).toEqual(['a2', 'b2', 'c2']) - }) - - it('deleting an object card reindexes the outer array', () => { - const data = makeData() - bootClient(data) - - const cardHeader = Array.from(document.querySelectorAll('.card-header')) - .find(h => h.textContent === 'details.services[1]') as HTMLElement - expect(cardHeader).not.toBeNull() - const card = cardHeader.closest('.obj-card') as HTMLElement - ;(card.querySelector('.btn-del-card') as HTMLButtonElement).click() - - const collected = (global as any).collect() - expect(collected.details.services).toHaveLength(2) - expect(collected.details.services[0].title).toBe('s0') - expect(collected.details.services[1].title).toBe('s2') - expect(collected.details.services[1].specs.items).toEqual(['a2', 'b2', 'c2']) - }) -}) diff --git a/proto/src/__tests__/cms-editor-shortcuts.test.ts b/proto/src/__tests__/cms-editor-shortcuts.test.ts deleted file mode 100644 index fb9ba26..0000000 --- a/proto/src/__tests__/cms-editor-shortcuts.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Regression tests for the Content Editor keyboard shortcuts (MITHOME-75). - * Runs the real scripts/cms-editor-client.js in jsdom, dispatches actual - * KeyboardEvents and asserts: - * 1. Ctrl+S calls save (fetch /save) and the browser save dialog is - * suppressed (preventDefault) - * 2. Ctrl+P calls publish - * 3. Ctrl+Shift+V opens the versions page in a new tab - * 4. '?' opens the shortcuts overlay, Esc closes it - * 5. plain 's' inside an input does NOT trigger save - */ -import fs from 'fs' -import path from 'path' - -const clientJs = fs.readFileSync(path.join(__dirname, '../../../scripts/cms-editor-client.js'), 'utf8') - + '\n' + fs.readFileSync(path.join(__dirname, '../../../scripts/cms-editor-shortcuts.js'), 'utf8') - -const makeData = () => ({ - hero: { title: 'T', subtitle: 'S', description: 'D' }, -}) - -function press(target: Document | Element, init: KeyboardEventInit) { - const ev = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, ...init }) - target.dispatchEvent(ev) - return ev -} - -const flush = () => new Promise(r => setTimeout(r, 0)) - -// Globals must exist BEFORE the eval — the client script boots immediately -// (render(DATA, …)). The eval runs ONCE: every eval would add another keydown -// listener to the shared jsdom document, and the toggle-style overlay handler -// would then open/close itself multiple times per keypress. -;(global as any).DATA = makeData() -;(global as any).FILE = 'home' -;(global as any).CSRF_TOKEN = 'boot' -;(global as any).CONTENT_HASH = 'x'.repeat(64) -;(global as any).fetch = jest.fn(async () => ({ status: 200, ok: true, json: async () => ({ ok: true }) })) -;(global as any).window = global -document.body.innerHTML = '
' -;(0, eval)(clientJs) - -describe('Content Editor keyboard shortcuts', () => { - let fetchCalls: Array<{ url: string; init?: RequestInit }> - - beforeEach(() => { - fetchCalls = [] - ;(global as any).DATA = makeData() - ;(global as any).FILE = 'home' - ;(global as any).CSRF_TOKEN = 'test-csrf' - ;(global as any).CONTENT_HASH = 'x'.repeat(64) - ;(global as any).fetch = jest.fn(async (url: string, init?: RequestInit) => { - fetchCalls.push({ url, init }) - return { status: 200, ok: true, json: async () => ({ ok: true, contentHash: 'y'.repeat(64) }) } - }) - ;(global as any).window = global - ;(window as any).open = jest.fn() - document.body.innerHTML = '
' - }) - - afterEach(() => { - delete (global as any).DATA - delete (global as any).FILE - delete (global as any).CSRF_TOKEN - delete (global as any).CONTENT_HASH - }) - - it('Ctrl+S saves via fetch and suppresses the browser save dialog', () => { - const ev = press(document, { key: 's', ctrlKey: true }) - expect(ev.defaultPrevented).toBe(true) - expect(fetchCalls.length).toBeGreaterThanOrEqual(1) - expect(fetchCalls[0].url).toContain('/save?file=home') - }) - - it('Cmd+P publishes', async () => { - const ev = press(document, { key: 'p', metaKey: true }) - expect(ev.defaultPrevented).toBe(true) - await flush() // publish awaits save() before its own fetch - expect(fetchCalls.some(c => c.url === '/publish')).toBe(true) - }) - - it('Ctrl+Shift+V opens the versions page in a new tab', () => { - const ev = press(document, { key: 'V', ctrlKey: true, shiftKey: true }) - expect(ev.defaultPrevented).toBe(true) - expect((window as any).open).toHaveBeenCalledWith('/versions?file=home', '_blank') - }) - - it("'?' opens the shortcuts overlay and Esc closes it", () => { - press(document, { key: '?' }) - expect(document.getElementById('shortcuts-overlay')).not.toBeNull() - press(document, { key: 'Escape' }) - expect(document.getElementById('shortcuts-overlay')).toBeNull() - }) - - it('plain typing in an input never triggers save', () => { - const input = document.createElement('input') - document.body.appendChild(input) - const ev = press(input, { key: 's' }) - expect(ev.defaultPrevented).toBe(false) - expect(fetchCalls.length).toBe(0) - }) -}) diff --git a/proto/src/content/schema.js b/proto/src/content/schema.js index 849fdcd..c2cf3db 100644 --- a/proto/src/content/schema.js +++ b/proto/src/content/schema.js @@ -1,5 +1,9 @@ -// Shared runtime schema for the content JSON files. -// Kept dependency-free so it can run in both Next.js and content-editor.js. +// Shared runtime schema for the content JSON files (proto/src/content/*.json). +// Kept dependency-free — used by src/content/index.ts (test fixtures for +// Header/Footer, MITHOME-96) and scripts/test-content-schema.js. The JSON +// files themselves remain the source for scripts/migrate-content-to-payload.ts. +// The standalone content-editor.js CMS that used to run this too was retired +// in MITHOME-93 (superseded by Payload CMS). const string = { type: 'string' }; const boolean = { type: 'boolean' }; const array = items => ({ type: 'array', items }); diff --git a/scripts/cms-core.js b/scripts/cms-core.js deleted file mode 100644 index ffa2058..0000000 --- a/scripts/cms-core.js +++ /dev/null @@ -1,135 +0,0 @@ -// Security and infrastructure helpers for the Content Editor, extracted so -// content-editor.js stays focused on HTTP routing (file-size limits). -// Dependencies (validateLogin, hasValidSession) are injected to avoid cycles. -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); - -const CMS_USER = process.env.CMS_USER; -const CMS_PASS = process.env.CMS_PASS; -const CMS_DEPLOY_ENV = process.env.CMS_DEPLOY_ENV; -const CSRF_TOKEN = process.env.CMS_CSRF_TOKEN || crypto.randomBytes(32).toString('hex'); -const rateLimits = new Map(); - -function securityConfigIsValid() { - return Boolean(CMS_USER && CMS_PASS && ['staging', 'production'].includes(CMS_DEPLOY_ENV)); -} - -function getClientAddress(req) { - // The editor only listens on 127.0.0.1; the staging Nginx proxy supplies this header. - // WHY: take the LAST entry. Nginx ($proxy_add_x_forwarded_for) appends the real client - // IP to the list, so the first entry may be a spoofed value sent by the client — using - // it would let attackers bypass the rate limiter with a fresh "IP" per request. - const forwarded = req.headers['x-forwarded-for']; - if (typeof forwarded === 'string' && forwarded.trim()) { - const parts = forwarded.split(',').map(part => part.trim()).filter(Boolean); - if (parts.length > 0) return parts[parts.length - 1]; - } - return req.socket.remoteAddress || 'unknown'; -} - -function isRateLimited(key, limit, windowMs) { - const now = Date.now(); - const attempts = (rateLimits.get(key) || []).filter(time => now - time < windowMs); - if (attempts.length === 0) { - rateLimits.delete(key); - return false; - } - rateLimits.set(key, attempts); - return attempts.length >= limit; -} - -function recordRateLimitAttempt(key, windowMs) { - const now = Date.now(); - const attempts = (rateLimits.get(key) || []).filter(time => now - time < windowMs); - attempts.push(now); - rateLimits.set(key, attempts); -} - -function exceedsRateLimit(key, limit, windowMs) { - const now = Date.now(); - const attempts = (rateLimits.get(key) || []).filter(time => now - time < windowMs); - attempts.push(now); - rateLimits.set(key, attempts); - return attempts.length > limit; -} - -function hasValidCredentials(req, validateLogin) { - const b64auth = (req.headers.authorization || '').split(' ')[1] || ''; - const str = Buffer.from(b64auth, 'base64').toString(); - const colonIdx = str.indexOf(':'); - const login = colonIdx !== -1 ? str.slice(0, colonIdx) : str; - const password = colonIdx !== -1 ? str.slice(colonIdx + 1) : ''; - return validateLogin(login, password, CMS_USER, CMS_PASS); -} - -function isBrowserNavigation(req) { - return req.method === 'GET' && String(req.headers.accept || '').includes('text/html'); -} - -// WHY: Safari (and other browsers) cache Basic Auth credentials and resend them -// automatically, which would let an already-logged-out browser straight back in. -// Browser navigations therefore authenticate ONLY via the session cookie, so -// logout is final. Non-browser requests (curl, API clients) keep Basic Auth. -function makeIsAuthenticated(hasValidSession, validateLogin) { - return function isAuthenticated(req) { - if (isBrowserNavigation(req)) return hasValidSession(req); - return hasValidCredentials(req, validateLogin) || hasValidSession(req); - }; -} - -function hasValidCsrfToken(req) { - const token = req.headers['x-csrf-token']; - return typeof token === 'string' - && token.length === CSRF_TOKEN.length - && crypto.timingSafeEqual(Buffer.from(token), Buffer.from(CSRF_TOKEN)); -} - -function makeWriteAudit(auditFile) { - return function writeAudit(event, details = {}) { - const record = { timestamp: new Date().toISOString(), event, ...details }; - fs.appendFileSync(auditFile, JSON.stringify(record) + '\n', { encoding: 'utf8', mode: 0o600 }); - }; -} - -function backupAndWriteAtomically(targetFile, data, backupDir) { - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - const backupName = `${path.basename(targetFile, '.json')}.${timestamp}.json`; - const backupFile = path.join(backupDir, backupName); - const tempFile = `${targetFile}.${process.pid}.${Date.now()}.tmp`; - - fs.mkdirSync(backupDir, { recursive: true, mode: 0o700 }); - fs.copyFileSync(targetFile, backupFile); - fs.writeFileSync(tempFile, JSON.stringify(data, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 }); - fs.renameSync(tempFile, targetFile); - return backupFile; -} - - -const RATE_LIMIT_GC_INTERVAL_MS = 5 * 60 * 1000; -setInterval(() => { - const now = Date.now(); - for (const [key, attempts] of rateLimits) { - const valid = attempts.filter(t => now - t < 15 * 60 * 1000); - if (valid.length === 0) rateLimits.delete(key); - else rateLimits.set(key, valid); - } -}, RATE_LIMIT_GC_INTERVAL_MS).unref(); - -module.exports = { - CMS_USER, - CMS_PASS, - CMS_DEPLOY_ENV, - CSRF_TOKEN, - securityConfigIsValid, - getClientAddress, - isRateLimited, - recordRateLimitAttempt, - exceedsRateLimit, - hasValidCredentials, - isBrowserNavigation, - makeIsAuthenticated, - hasValidCsrfToken, - makeWriteAudit, - backupAndWriteAtomically, -}; diff --git a/scripts/cms-diff.js b/scripts/cms-diff.js deleted file mode 100644 index 63fe544..0000000 --- a/scripts/cms-diff.js +++ /dev/null @@ -1,65 +0,0 @@ -// Dependency-free line diff (LCS) for the CMS version comparison view. -// Input lines are plain text; output entries are typed add/del/ctx rows. - -function diffLines(oldLines, newLines) { - const n = oldLines.length; - const m = newLines.length; - // LCS lengths DP (files are small, a few hundred lines — O(n*m) is fine) - const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); - for (let i = n - 1; i >= 0; i--) { - for (let j = m - 1; j >= 0; j--) { - dp[i][j] = oldLines[i] === newLines[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]); - } - } - const out = []; - let i = 0; - let j = 0; - while (i < n && j < m) { - if (oldLines[i] === newLines[j]) { - out.push({ type: 'ctx', text: oldLines[i] }); - i++; - j++; - } else if (dp[i + 1][j] >= dp[i][j + 1]) { - out.push({ type: 'del', text: oldLines[i] }); - i++; - } else { - out.push({ type: 'add', text: newLines[j] }); - j++; - } - } - while (i < n) { out.push({ type: 'del', text: oldLines[i] }); i++; } - while (j < m) { out.push({ type: 'add', text: newLines[j] }); j++; } - return out; -} - -function escapeHtml(value) { - return String(value) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"'); -} - -// Keep only ±contextAround context lines around changes to keep pages small. -function trimContext(entries, contextAround = 3) { - const keep = new Array(entries.length).fill(false); - entries.forEach((e, idx) => { - if (e.type !== 'ctx') { - for (let k = Math.max(0, idx - contextAround); k <= Math.min(entries.length - 1, idx + contextAround); k++) keep[k] = true; - } - }); - const out = []; - let skipping = false; - entries.forEach((e, idx) => { - if (keep[idx]) { out.push(e); skipping = false; } - else if (!skipping) { out.push({ type: 'skip', text: '…' }); skipping = true; } - }); - return out; -} - -function renderDiffHtml(oldText, newText) { - const entries = trimContext(diffLines(oldText.split('\n'), newText.split('\n'))); - return entries.map(e => `
${escapeHtml(e.text) || ' '}
`).join('\n'); -} - -module.exports = { diffLines, trimContext, renderDiffHtml, escapeHtml }; diff --git a/scripts/cms-editor-client.js b/scripts/cms-editor-client.js deleted file mode 100644 index a2e6c27..0000000 --- a/scripts/cms-editor-client.js +++ /dev/null @@ -1,345 +0,0 @@ -// Browser-side script of the Content Editor editor page. -// Inlined into the HTML template at render time by content-editor.js. -// Test coverage: scripts/test-content-editor-serializer.js runs this exact code. - -// ── Render ────────────────────────────────────────────────────────────────── - -function render(obj, container) { - container.innerHTML = ''; - renderObject(obj, container, ''); -} - -function renderObject(obj, container, prefix) { - for (const [key, val] of Object.entries(obj)) { - const path = prefix ? prefix + '.' + key : key; - if (Array.isArray(val)) { - renderArray(key, val, container, path); - } else if (typeof val === 'object' && val !== null) { - renderObject(val, container, path); - } else { - renderPrimitive(path, val, container); - } - } -} - -function renderPrimitive(path, val, container) { - const isLong = String(val).length > 80 || String(val).includes('<'); - const div = document.createElement('div'); - div.className = 'field'; - const type = val === null ? 'null' : typeof val; - let control; - if (type === 'boolean') { - control = ``; - } else if (type === 'number') { - control = ``; - } else { - control = isLong - ? `