mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
276 lines
7.5 KiB
276 lines
7.5 KiB
const Api = require('nocodb-sdk').Api; |
|
const { UITypes } = require('nocodb-sdk'); |
|
const jsonfile = require('jsonfile'); |
|
|
|
const GRID = 3, GALLERY = 2, FORM = 1; |
|
|
|
let ncMap = { /* id: name <string> */ }; |
|
let tblSchema = []; |
|
let api = {}; |
|
let viewStore = { columns: {}, sort: {}, filter: {} }; |
|
|
|
let inputConfig = jsonfile.readFileSync(`config.json`) |
|
let ncConfig = { |
|
projectName: inputConfig.srcProject, |
|
baseURL: inputConfig.baseURL, |
|
headers: { |
|
'xc-auth': `${inputConfig["xc-auth"]}` |
|
} |
|
}; |
|
|
|
|
|
// helper routines |
|
// remove objects containing 0/ false/ null |
|
// fixme: how to handle when cdf (default value) is configured as 0/ null/ false |
|
function removeEmpty(obj) { |
|
return Object.fromEntries( |
|
Object.entries(obj) |
|
.filter(([_, v]) => v != null && v != 0 && v != false) |
|
.map(([k, v]) => [k, v === Object(v) ? removeEmpty(v) : v]) |
|
); |
|
} |
|
|
|
|
|
function addColumnSpecificData(c) { |
|
// pick required fields to proceed further |
|
let col; |
|
if(inputConfig.excludeDt) { |
|
col = removeEmpty( |
|
(({ id, title, column_name, uidt, pk, pv, rqd, dtxp, system, ai }) => ({ |
|
id, title, column_name, uidt, pk, pv, rqd, dtxp, system, ai |
|
}))(c) |
|
); |
|
} else { |
|
col = removeEmpty( |
|
(({ id, title, column_name, uidt, dt, pk, pv, rqd, dtxp, system, ai }) => ({ |
|
id, title, column_name, uidt, dt, pk, pv, rqd, dtxp, system, ai |
|
}))(c) |
|
); |
|
} |
|
|
|
switch (c.uidt) { |
|
case UITypes.Formula: |
|
col.formula = c.colOptions.formula; |
|
col.formula_raw = c.colOptions.formula_raw; |
|
break; |
|
case UITypes.LinkToAnotherRecord: |
|
col[`colOptions`] = { |
|
fk_model_id: c.fk_model_id, |
|
fk_related_model_id: c.colOptions.fk_related_model_id, |
|
fk_child_column_id: c.colOptions.fk_child_column_id, |
|
fk_parent_column_id: c.colOptions.fk_parent_column_id, |
|
type: c.colOptions.type |
|
}; |
|
break; |
|
case UITypes.Lookup: |
|
col[`colOptions`] = { |
|
fk_model_id: c.fk_model_id, |
|
fk_relation_column_id: c.colOptions.fk_relation_column_id, |
|
fk_lookup_column_id: c.colOptions.fk_lookup_column_id |
|
}; |
|
break; |
|
case UITypes.Rollup: |
|
col[`colOptions`] = { |
|
fk_model_id: c.fk_model_id, |
|
fk_relation_column_id: c.colOptions.fk_relation_column_id, |
|
fk_rollup_column_id: c.colOptions.fk_rollup_column_id, |
|
rollup_function: c.colOptions.rollup_function |
|
}; |
|
break; |
|
} |
|
|
|
return col; |
|
} |
|
|
|
function addViewDetails(v) { |
|
// pick required fields to proceed further |
|
let view = (({ id, title, type, show_system_fields, lock_type, order }) => ({ |
|
id, |
|
title, |
|
type, |
|
show_system_fields, |
|
lock_type, |
|
order |
|
}))(v); |
|
|
|
// form view |
|
if (v.type === FORM) { |
|
view.property = (({ |
|
heading, |
|
subheading, |
|
success_msg, |
|
redirect_after_secs, |
|
email, |
|
submit_another_form, |
|
show_blank_form |
|
}) => ({ |
|
heading, |
|
subheading, |
|
success_msg, |
|
redirect_after_secs, |
|
email, |
|
submit_another_form, |
|
show_blank_form |
|
}))(v.view); |
|
} |
|
|
|
// gallery view |
|
else if (v.type === GALLERY) { |
|
view.property = { |
|
fk_cover_image_col_id: ncMap[v.view.fk_cover_image_col_id] |
|
}; |
|
} |
|
|
|
// gallery view doesn't share column information in api yet |
|
if (v.type !== GALLERY) { |
|
if (v.type === GRID) |
|
view.columns = viewStore.columns[v.id].map(a => |
|
(({ id, width, order, show }) => ({ id, width, order, show }))(a) |
|
); |
|
if (v.type === FORM) |
|
view.columns = viewStore.columns[v.id].map(a => |
|
(({ id, order, show, label, help, description, required }) => ({ |
|
id, |
|
order, |
|
show, |
|
label, |
|
help, |
|
description, |
|
required |
|
}))(a) |
|
); |
|
|
|
for (let i = 0; i < view.columns?.length; i++) |
|
view.columns[i].title = ncMap[viewStore.columns[v.id][i].id]; |
|
|
|
// skip hm & mm columns |
|
view.columns = view.columns |
|
?.filter(a => a.title?.includes('_nc_m2m_') === false) |
|
.filter(a => a.title?.includes('nc_') === false); |
|
} |
|
|
|
// filter & sort configurations |
|
if (v.type !== FORM) { |
|
view.sort = viewStore.sort[v.id].map(a => |
|
(({ fk_column_id, direction, order }) => ({ |
|
fk_column_id, |
|
direction, |
|
order |
|
}))(a) |
|
); |
|
view.filter = viewStore.filter[v.id].map(a => |
|
(({ fk_column_id, logical_op, comparison_op, value, order }) => ({ |
|
fk_column_id, |
|
logical_op, |
|
comparison_op, |
|
value, |
|
order |
|
}))(a) |
|
); |
|
} |
|
return view; |
|
} |
|
|
|
// view data stored as is for quick access |
|
async function storeViewDetails(tableId) { |
|
// read view data for each table |
|
let viewList = await api.dbView.list(tableId); |
|
for (let j = 0; j < viewList.list.length; j++) { |
|
let v = viewList.list[j]; |
|
let viewDetails = []; |
|
|
|
// invoke view specific read to populate columns information |
|
if (v.type === FORM) viewDetails = (await api.dbView.formRead(v.id)).columns; |
|
else if (v.type === GALLERY) viewDetails = await api.dbView.galleryRead(v.id); |
|
else if (v.type === GRID) viewDetails = await api.dbView.gridColumnsList(v.id); |
|
viewStore.columns[v.id] = viewDetails; |
|
|
|
// populate sort information |
|
let vSort = await api.dbTableSort.list(v.id); |
|
viewStore.sort[v.id] = vSort.sorts.list; |
|
|
|
let vFilter = await api.dbTableFilter.read(v.id); |
|
viewStore.filter[v.id] = vFilter; |
|
} |
|
} |
|
|
|
// mapping table for quick information access |
|
// store maps for tableId, columnId, viewColumnId & viewId to their names |
|
async function generateMapTbl(pId) { |
|
const tblList = await api.dbTable.list(pId); |
|
|
|
for (let i = 0; i < tblList.list.length; i++) { |
|
let tblId = tblList.list[i].id; |
|
let tbl = await api.dbTable.read(tblId); |
|
|
|
// table ID <> name |
|
ncMap[tblId] = tbl.title; |
|
|
|
// column ID <> name |
|
tbl.columns.map(x => (ncMap[x.id] = x.title)); |
|
|
|
// view ID <> name |
|
tbl.views.map(x => (ncMap[x.id] = x.tn)); |
|
|
|
for (let i = 0; i < tbl.views.length; i++) { |
|
let x = tbl.views[i]; |
|
let viewColumns = []; |
|
if (x.type === FORM) viewColumns = (await api.dbView.formRead(x.id)).columns; |
|
else if (x.type === GALLERY) |
|
viewColumns = (await api.dbView.galleryRead(x.id)).columns; |
|
else if (x.type === GRID) viewColumns = await api.dbView.gridColumnsList(x.id); |
|
|
|
// view column ID <> name |
|
viewColumns?.map(a => (ncMap[a.id] = ncMap[a.fk_column_id])); |
|
} |
|
} |
|
} |
|
|
|
// main |
|
// |
|
async function exportSchema() { |
|
api = new Api(ncConfig); |
|
|
|
// fetch project details (id et.al) |
|
const x = await api.project.list(); |
|
const p = x.list.find(a => a.title === ncConfig.projectName); |
|
|
|
await generateMapTbl(p.id); |
|
|
|
// read project |
|
const tblList = await api.dbTable.list(p.id); |
|
|
|
// for each table |
|
for (let i = 0; i < tblList.list.length; i++) { |
|
let tblId = tblList.list[i].id; |
|
await storeViewDetails(tblId); |
|
|
|
let tbl = await api.dbTable.read(tblId); |
|
|
|
// prepare schema |
|
let tSchema = { |
|
id: tbl.id, |
|
title: tbl.title, |
|
table_name: tbl?.table_name, |
|
columns: [...tbl.columns.map(c => addColumnSpecificData(c))] |
|
.filter(a => a.title.includes('_nc_m2m_') === false) // mm |
|
.filter(a => a.title.includes(p.prefix) === false) // hm |
|
.filter( |
|
a => !(a?.system === 1 && a.uidt === UITypes.LinkToAnotherRecord) |
|
), |
|
views: [...tbl.views.map(v => addViewDetails(v))] |
|
}; |
|
tblSchema.push(tSchema); |
|
} |
|
} |
|
|
|
(async () => { |
|
await exportSchema(); |
|
jsonfile.writeFileSync( |
|
`${ncConfig.projectName.replace(/ /g, '_')}.json`, |
|
tblSchema, |
|
{ spaces: 2 } |
|
); |
|
})().catch(e => { |
|
console.log(e); |
|
});
|
|
|