#!/usr/bin/env node /** * Sync brands from local NocoDB to a remote NocoDB instance. * * Usage: * REMOTE_NOCODB_URL=https://... REMOTE_NOCODB_TOKEN=... REMOTE_NOCODB_BASE_ID=... node sync-brands.js * * Reads local config from .env (NOCODB_URL, NOCODB_TOKEN, NOCODB_BASE_ID). * Syncs all brands from local → remote (upserts by name). */ import 'dotenv/config' const LOCAL_URL = process.env.NOCODB_URL || 'http://localhost:8090' const LOCAL_TOKEN = process.env.NOCODB_TOKEN const LOCAL_BASE_ID = process.env.NOCODB_BASE_ID const REMOTE_URL = process.env.REMOTE_NOCODB_URL const REMOTE_TOKEN = process.env.REMOTE_NOCODB_TOKEN const REMOTE_BASE_ID = process.env.REMOTE_NOCODB_BASE_ID if (!REMOTE_URL || !REMOTE_TOKEN || !REMOTE_BASE_ID) { console.error('Missing env vars: REMOTE_NOCODB_URL, REMOTE_NOCODB_TOKEN, REMOTE_NOCODB_BASE_ID') process.exit(1) } async function resolveTableId(baseUrl, token, baseId, tableName) { const res = await fetch(`${baseUrl}/api/v2/meta/bases/${baseId}/tables`, { headers: { 'xc-token': token }, }) const data = await res.json() const table = (data.list || []).find(t => t.title === tableName) if (!table) throw new Error(`Table "${tableName}" not found`) return table.id } async function listRecords(baseUrl, token, tableId) { const res = await fetch(`${baseUrl}/api/v2/tables/${tableId}/records?limit=200`, { headers: { 'xc-token': token }, }) const data = await res.json() return data.list || [] } async function createRecord(baseUrl, token, tableId, record) { const res = await fetch(`${baseUrl}/api/v2/tables/${tableId}/records`, { method: 'POST', headers: { 'xc-token': token, 'Content-Type': 'application/json' }, body: JSON.stringify(record), }) return res.json() } async function updateRecord(baseUrl, token, tableId, record) { const res = await fetch(`${baseUrl}/api/v2/tables/${tableId}/records`, { method: 'PATCH', headers: { 'xc-token': token, 'Content-Type': 'application/json' }, body: JSON.stringify(record), }) return res.json() } async function main() { console.log('Resolving table IDs...') const localTableId = await resolveTableId(LOCAL_URL, LOCAL_TOKEN, LOCAL_BASE_ID, 'Brands') const remoteTableId = await resolveTableId(REMOTE_URL, REMOTE_TOKEN, REMOTE_BASE_ID, 'Brands') console.log('Fetching local brands...') const localBrands = await listRecords(LOCAL_URL, LOCAL_TOKEN, localTableId) console.log(` Found ${localBrands.length} local brands`) console.log('Fetching remote brands...') const remoteBrands = await listRecords(REMOTE_URL, REMOTE_TOKEN, remoteTableId) console.log(` Found ${remoteBrands.length} remote brands`) const remoteByName = new Map(remoteBrands.map(b => [b.name, b])) const FIELDS = ['name', 'name_ar', 'priority', 'color', 'icon', 'category', 'logo'] let created = 0, updated = 0, skipped = 0 for (const brand of localBrands) { const data = {} for (const f of FIELDS) { if (brand[f] !== undefined && brand[f] !== null) data[f] = brand[f] } const existing = remoteByName.get(brand.name) if (existing) { // Check if anything changed const needsUpdate = FIELDS.some(f => brand[f] !== existing[f]) if (needsUpdate) { await updateRecord(REMOTE_URL, REMOTE_TOKEN, remoteTableId, { Id: existing.Id, ...data }) console.log(` Updated: ${brand.name}`) updated++ } else { skipped++ } } else { await createRecord(REMOTE_URL, REMOTE_TOKEN, remoteTableId, data) console.log(` Created: ${brand.name}`) created++ } } console.log(`\nDone! Created: ${created}, Updated: ${updated}, Skipped (no changes): ${skipped}`) } main().catch(err => { console.error('Sync failed:', err) process.exit(1) })