From fb7f1809f2cf5d8645c547790e17c6454d5ff61f Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Mon, 2 Mar 2026 15:41:24 +0100 Subject: [PATCH] New Workflow to push json to pocketbase --- .github/workflows/push_json_to_pocketbase.yml | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 .github/workflows/push_json_to_pocketbase.yml diff --git a/.github/workflows/push_json_to_pocketbase.yml b/.github/workflows/push_json_to_pocketbase.yml new file mode 100644 index 000000000..2d8fead79 --- /dev/null +++ b/.github/workflows/push_json_to_pocketbase.yml @@ -0,0 +1,173 @@ +name: Push JSON changes to PocketBase + +on: + push: + branches: + - main + paths: + - "frontend/public/json/**" + +jobs: + push-json: + runs-on: self-hosted + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get changed JSON files with slug + id: changed + run: | + changed=$(git diff --name-only "${{ github.event.before }}" "${{ github.event.after }}" -- frontend/public/json/ | grep '\.json$' || true) + with_slug="" + for f in $changed; do + [[ -f "$f" ]] || continue + jq -e '.slug' "$f" >/dev/null 2>&1 && with_slug="$with_slug $f" + done + with_slug=$(echo $with_slug | xargs -n1) + if [[ -z "$with_slug" ]]; then + echo "No app JSON files changed (or no files with slug)." + echo "count=0" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "$with_slug" > changed_app_jsons.txt + echo "count=$(echo "$with_slug" | wc -w)" >> "$GITHUB_OUTPUT" + + - name: Push to PocketBase + if: steps.changed.outputs.count != '0' + env: + POCKETBASE_URL: ${{ secrets.POCKETBASE_URL }} + POCKETBASE_COLLECTION: ${{ secrets.POCKETBASE_COLLECTION }} + POCKETBASE_ADMIN_EMAIL: ${{ secrets.POCKETBASE_ADMIN_EMAIL }} + POCKETBASE_ADMIN_PASSWORD: ${{ secrets.POCKETBASE_ADMIN_PASSWORD }} + run: | + node << 'ENDSCRIPT' + (async function() { + const fs = require('fs'); + const https = require('https'); + const http = require('http'); + const url = require('url'); + function request(fullUrl, opts) { + return new Promise(function(resolve, reject) { + const u = url.parse(fullUrl); + const isHttps = u.protocol === 'https:'; + const body = opts.body; + const options = { + hostname: u.hostname, + port: u.port || (isHttps ? 443 : 80), + path: u.path, + method: opts.method || 'GET', + headers: opts.headers || {} + }; + if (body) options.headers['Content-Length'] = Buffer.byteLength(body); + const lib = isHttps ? https : http; + const req = lib.request(options, function(res) { + let data = ''; + res.on('data', function(chunk) { data += chunk; }); + res.on('end', function() { + resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: data }); + }); + }); + req.on('error', reject); + if (body) req.write(body); + req.end(); + }); + } + const raw = process.env.POCKETBASE_URL.replace(/\/$/, ''); + const apiBase = /\/api$/i.test(raw) ? raw : raw + '/api'; + const coll = process.env.POCKETBASE_COLLECTION; + const files = fs.readFileSync('changed_app_jsons.txt', 'utf8').trim().split(/\s+/).filter(Boolean); + const authUrl = apiBase + '/collections/users/auth-with-password'; + console.log('Auth URL: ' + authUrl); + const authBody = JSON.stringify({ + identity: process.env.POCKETBASE_ADMIN_EMAIL, + password: process.env.POCKETBASE_ADMIN_PASSWORD + }); + const authRes = await request(authUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: authBody + }); + if (!authRes.ok) { + throw new Error('Auth failed. Tried: ' + authUrl + ' - Verify POST to that URL with body {"identity":"...","password":"..."} works. Response: ' + authRes.body); + } + const token = JSON.parse(authRes.body).token; + const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records'; + let categoryIdToName = {}; + try { + const metadata = JSON.parse(fs.readFileSync('frontend/public/json/metadata.json', 'utf8')); + (metadata.categories || []).forEach(function(cat) { categoryIdToName[cat.id] = cat.name; }); + } catch (e) { console.warn('Could not load metadata.json:', e.message); } + let typeValueToId = {}; + let categoryNameToPbId = {}; + try { + const typesRes = await request(apiBase + '/collections/z_ref_script_types/records?perPage=500', { headers: { 'Authorization': token } }); + if (typesRes.ok) { + const typesData = JSON.parse(typesRes.body); + (typesData.items || []).forEach(function(item) { if (item.type) typeValueToId[item.type] = item.id; }); + } + } catch (e) { console.warn('Could not fetch z_ref_script_types:', e.message); } + try { + const catRes = await request(apiBase + '/collections/script_categories/records?perPage=500', { headers: { 'Authorization': token } }); + if (catRes.ok) { + const catData = JSON.parse(catRes.body); + (catData.items || []).forEach(function(item) { if (item.name) categoryNameToPbId[item.name] = item.id; }); + } + } catch (e) { console.warn('Could not fetch script_categories:', e.message); } + for (const file of files) { + if (!fs.existsSync(file)) continue; + const data = JSON.parse(fs.readFileSync(file, 'utf8')); + if (!data.slug) { console.log('Skipping', file, '(no slug)'); continue; } + var payload = { + name: data.name, + slug: data.slug, + script_created: data.date_created || data.script_created, + script_updated: data.date_created || data.script_updated, + updateable: data.updateable, + privileged: data.privileged, + port: data.interface_port != null ? data.interface_port : data.port, + documentation: data.documentation, + website: data.website, + logo: data.logo, + description: data.description, + config_path: data.config_path, + default_user: (data.default_credentials && data.default_credentials.username) || data.default_user, + default_passwd: (data.default_credentials && data.default_credentials.password) || data.default_passwd, + is_dev: true + }; + var resolvedType = typeValueToId[data.type]; + if (resolvedType) payload.type = resolvedType; + var resolvedCats = (data.categories || []).map(function(n) { return categoryNameToPbId[categoryIdToName[n]]; }).filter(Boolean); + if (resolvedCats.length) payload.categories = resolvedCats; + if (data.version !== undefined) payload.version = data.version; + if (data.changelog !== undefined) payload.changelog = data.changelog; + if (data.screenshots !== undefined) payload.screenshots = data.screenshots; + const filter = "(slug='" + data.slug + "')"; + const listRes = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', { + headers: { 'Authorization': token } + }); + const list = JSON.parse(listRes.body); + const existingId = list.items && list.items[0] && list.items[0].id; + if (existingId) { + console.log('Updating', file, '(slug=' + data.slug + ')'); + const r = await request(recordsUrl + '/' + existingId, { + method: 'PATCH', + headers: { 'Authorization': token, 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + if (!r.ok) throw new Error('PATCH failed: ' + r.body); + } else { + console.log('Creating', file, '(slug=' + data.slug + ')'); + const r = await request(recordsUrl, { + method: 'POST', + headers: { 'Authorization': token, 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + if (!r.ok) throw new Error('POST failed: ' + r.body); + } + } + console.log('Done.'); + })().catch(e => { console.error(e); process.exit(1); }); + ENDSCRIPT + shell: bash