mirror of https://github.com/nocodb/nocodb
Pranav C
2 years ago
17 changed files with 688 additions and 146 deletions
@ -0,0 +1,59 @@
|
||||
// // Project CRUD
|
||||
import { Request, Response } from 'express'; |
||||
import ncMetaAclMw from '../meta/helpers/ncMetaAclMw'; |
||||
import catchError from '../meta/helpers/catchError'; |
||||
import { utilService } from '../services'; |
||||
|
||||
export async function testConnection(req: Request, res: Response) { |
||||
res.json(await utilService.testConnection({ body: req.body })); |
||||
} |
||||
|
||||
export async function appInfo(req: Request, res: Response) { |
||||
res.json( |
||||
await utilService.appInfo({ |
||||
req: { |
||||
ncSiteUrl: (req as any).ncSiteUrl, |
||||
}, |
||||
}) |
||||
); |
||||
} |
||||
|
||||
export async function versionInfo(_req: Request, res: Response) { |
||||
res.json(await utilService.versionInfo()); |
||||
} |
||||
|
||||
export async function appHealth(_: Request, res: Response) { |
||||
res.json(await utilService.appHealth()); |
||||
} |
||||
|
||||
export async function axiosRequestMake(req: Request, res: Response) { |
||||
res.json(await utilService.axiosRequestMake({ body: req.body })); |
||||
} |
||||
|
||||
export async function aggregatedMetaInfo(_req: Request, res: Response) { |
||||
res.json(await utilService.aggregatedMetaInfo()); |
||||
} |
||||
|
||||
export async function urlToDbConfig(req: Request, res: Response) { |
||||
res.json( |
||||
await utilService.urlToDbConfig({ |
||||
body: req.body, |
||||
}) |
||||
); |
||||
} |
||||
|
||||
export default (router) => { |
||||
router.post( |
||||
'/api/v1/db/meta/connection/test', |
||||
ncMetaAclMw(testConnection, 'testConnection') |
||||
); |
||||
router.get('/api/v1/db/meta/nocodb/info', catchError(appInfo)); |
||||
router.post('/api/v1/db/meta/axiosRequestMake', catchError(axiosRequestMake)); |
||||
router.get('/api/v1/version', catchError(versionInfo)); |
||||
router.get('/api/v1/health', catchError(appHealth)); |
||||
router.post('/api/v1/url_to_config', catchError(urlToDbConfig)); |
||||
router.get( |
||||
'/api/v1/aggregated-meta-info', |
||||
ncMetaAclMw(aggregatedMetaInfo, 'aggregatedMetaInfo') |
||||
); |
||||
}; |
@ -0,0 +1,13 @@
|
||||
import { FormViewColumn } from '../models'; |
||||
import { Tele } from 'nc-help'; |
||||
export async function columnUpdate(param: { |
||||
formViewColumnId: string; |
||||
// todo: replace with FormColumnReq
|
||||
formViewColumn: FormViewColumn; |
||||
}) { |
||||
Tele.emit('evt', { evt_type: 'formViewColumn:updated' }); |
||||
return await FormViewColumn.update( |
||||
param.formViewColumnId, |
||||
param.formViewColumn |
||||
); |
||||
} |
@ -0,0 +1,31 @@
|
||||
import { Tele } from 'nc-help'; |
||||
import { FormReqType, ViewTypes } from 'nocodb-sdk'; |
||||
import { FormView, View } from '../models'; |
||||
|
||||
export async function formViewGet(param: { formViewId: string }) { |
||||
const formViewData = await FormView.getWithInfo(param.formViewId); |
||||
return formViewData; |
||||
} |
||||
|
||||
export async function formViewCreate(param: { |
||||
tableId: string; |
||||
body: FormReqType; |
||||
}) { |
||||
Tele.emit('evt', { evt_type: 'vtable:created', show_as: 'form' }); |
||||
const view = await View.insert({ |
||||
...param.body, |
||||
// todo: sanitize
|
||||
fk_model_id: param.tableId, |
||||
type: ViewTypes.FORM, |
||||
}); |
||||
return view; |
||||
} |
||||
|
||||
// @ts-ignore
|
||||
export async function formViewUpdate(param: { |
||||
formViewId: string; |
||||
body: FormReqType; |
||||
}) { |
||||
Tele.emit('evt', { evt_type: 'view:updated', type: 'grid' }); |
||||
await FormView.update(param.formViewId, param.body); |
||||
} |
@ -0,0 +1,31 @@
|
||||
import { Request, Response, Router } from 'express'; |
||||
import { GalleryReqType, GalleryType, ViewTypes } from 'nocodb-sdk'; |
||||
import View from '../models/View'; |
||||
import GalleryView from '../models/GalleryView'; |
||||
import { Tele } from 'nc-help'; |
||||
|
||||
export async function galleryViewGet(param: { galleryViewId: string }) { |
||||
return await GalleryView.get(param.galleryViewId); |
||||
} |
||||
|
||||
export async function galleryViewCreate(param: { |
||||
tableId: string; |
||||
gallery: GalleryReqType; |
||||
}) { |
||||
Tele.emit('evt', { evt_type: 'vtable:created', show_as: 'gallery' }); |
||||
const view = await View.insert({ |
||||
...param.gallery, |
||||
// todo: sanitize
|
||||
fk_model_id: param.tableId, |
||||
type: ViewTypes.GALLERY, |
||||
}); |
||||
return view; |
||||
} |
||||
|
||||
export async function galleryViewUpdate(param: { |
||||
galleryViewId: string; |
||||
gallery: GalleryReqType; |
||||
}) { |
||||
Tele.emit('evt', { evt_type: 'view:updated', type: 'gallery' }); |
||||
await GalleryView.update(param.galleryViewId, param.gallery); |
||||
} |
@ -0,0 +1,26 @@
|
||||
import { Tele } from 'nc-help'; |
||||
import { GridReqType, ViewTypes } from 'nocodb-sdk'; |
||||
import { View } from '../models'; |
||||
import { GridView } from '../models'; |
||||
|
||||
export async function gridViewCreate(param: { |
||||
tableId: string; |
||||
grid: GridReqType; |
||||
}) { |
||||
const view = await View.insert({ |
||||
...param.grid, |
||||
// todo: sanitize
|
||||
fk_model_id: param.tableId, |
||||
type: ViewTypes.GRID, |
||||
}); |
||||
Tele.emit('evt', { evt_type: 'vtable:created', show_as: 'grid' }); |
||||
return view; |
||||
} |
||||
|
||||
export async function gridViewUpdate(param: { |
||||
viewId: string; |
||||
grid: GridReqType; |
||||
}) { |
||||
Tele.emit('evt', { evt_type: 'view:updated', type: 'grid' }); |
||||
return await GridView.update(param.viewId, param.grid); |
||||
} |
@ -0,0 +1,29 @@
|
||||
import { KanbanReqType, ViewTypes } from 'nocodb-sdk'; |
||||
import { KanbanView, View } from '../models'; |
||||
import { Tele } from 'nc-help'; |
||||
|
||||
export async function kanbanViewGet(param: { kanbanViewId: string }) { |
||||
return await KanbanView.get(param.kanbanViewId); |
||||
} |
||||
|
||||
export async function kanbanViewCreate(param: { |
||||
tableId: string; |
||||
kanban: KanbanReqType; |
||||
}) { |
||||
Tele.emit('evt', { evt_type: 'vtable:created', show_as: 'kanban' }); |
||||
const view = await View.insert({ |
||||
...param.kanban, |
||||
// todo: sanitize
|
||||
fk_model_id: param.tableId, |
||||
type: ViewTypes.KANBAN, |
||||
}); |
||||
return view; |
||||
} |
||||
|
||||
export async function kanbanViewUpdate(param: { |
||||
kanbanViewId: string; |
||||
kanban: KanbanReqType; |
||||
}) { |
||||
Tele.emit('evt', { evt_type: 'view:updated', type: 'kanban' }); |
||||
return await KanbanView.update(param.kanbanViewId, param.kanban); |
||||
} |
@ -0,0 +1,30 @@
|
||||
import { Tele } from 'nc-help'; |
||||
import { Plugin } from '../models' |
||||
import { PluginTestReqType, PluginType } from 'nocodb-sdk' |
||||
import NcPluginMgrv2 from '../meta/helpers/NcPluginMgrv2'; |
||||
|
||||
export async function pluginList() { |
||||
return await Plugin.list() |
||||
} |
||||
|
||||
export async function pluginTest(param:{body: PluginTestReqType}) { |
||||
Tele.emit('evt', { evt_type: 'plugin:tested' }); |
||||
return await NcPluginMgrv2.test(param.body) |
||||
} |
||||
|
||||
export async function pluginRead(param: { pluginId: string }) { |
||||
return await Plugin.get(param.pluginId) |
||||
} |
||||
export async function pluginUpdate( |
||||
param: { pluginId: string; plugin: PluginType } |
||||
) { |
||||
const plugin = await Plugin.update(param.pluginId, param.plugin); |
||||
Tele.emit('evt', { |
||||
evt_type: plugin.active ? 'plugin:installed' : 'plugin:uninstalled', |
||||
title: plugin.title, |
||||
}); |
||||
return plugin |
||||
} |
||||
export async function isPluginActive(param: { pluginTitle: string }) { |
||||
return await Plugin.isPluginActive(param.pluginTitle) |
||||
} |
@ -0,0 +1,381 @@
|
||||
import { compareVersions, validate } from 'compare-versions'; |
||||
|
||||
import { ViewTypes } from 'nocodb-sdk'; |
||||
import { Project } from '../models'; |
||||
import { NcError } from '../meta/helpers/catchError'; |
||||
import Noco from '../Noco'; |
||||
import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2'; |
||||
import { MetaTable } from '../utils/globals'; |
||||
import { packageVersion } from '../utils/packageVersion'; |
||||
import SqlMgrv2 from '../db/sql-mgr/v2/SqlMgrv2'; |
||||
import NcConfigFactory, { |
||||
defaultConnectionConfig, |
||||
} from '../utils/NcConfigFactory'; |
||||
import { User } from '../models'; |
||||
import axios from 'axios'; |
||||
import { NC_ATTACHMENT_FIELD_SIZE } from '../constants'; |
||||
|
||||
const versionCache = { |
||||
releaseVersion: null, |
||||
lastFetched: null, |
||||
}; |
||||
|
||||
|
||||
|
||||
export async function testConnection(param:{body: any}) { |
||||
return await SqlMgrv2.testConnection(param.body); |
||||
} |
||||
|
||||
export async function appInfo(param: { req: { ncSiteUrl: string } }) { |
||||
const projectHasAdmin = !(await User.isFirst()); |
||||
const result = { |
||||
authType: 'jwt', |
||||
projectHasAdmin, |
||||
firstUser: !projectHasAdmin, |
||||
type: 'rest', |
||||
env: process.env.NODE_ENV, |
||||
googleAuthEnabled: !!( |
||||
process.env.NC_GOOGLE_CLIENT_ID && process.env.NC_GOOGLE_CLIENT_SECRET |
||||
), |
||||
githubAuthEnabled: !!( |
||||
process.env.NC_GITHUB_CLIENT_ID && process.env.NC_GITHUB_CLIENT_SECRET |
||||
), |
||||
oneClick: !!process.env.NC_ONE_CLICK, |
||||
connectToExternalDB: !process.env.NC_CONNECT_TO_EXTERNAL_DB_DISABLED, |
||||
version: packageVersion, |
||||
defaultLimit: Math.max( |
||||
Math.min( |
||||
+process.env.DB_QUERY_LIMIT_DEFAULT || 25, |
||||
+process.env.DB_QUERY_LIMIT_MAX || 100 |
||||
), |
||||
+process.env.DB_QUERY_LIMIT_MIN || 1 |
||||
), |
||||
timezone: defaultConnectionConfig.timezone, |
||||
ncMin: !!process.env.NC_MIN, |
||||
teleEnabled: process.env.NC_DISABLE_TELE === 'true' ? false : true, |
||||
auditEnabled: process.env.NC_DISABLE_AUDIT === 'true' ? false : true, |
||||
ncSiteUrl: (param.req as any).ncSiteUrl, |
||||
ee: Noco.isEE(), |
||||
ncAttachmentFieldSize: NC_ATTACHMENT_FIELD_SIZE, |
||||
ncMaxAttachmentsAllowed: +(process.env.NC_MAX_ATTACHMENTS_ALLOWED || 10), |
||||
}; |
||||
|
||||
return result; |
||||
} |
||||
|
||||
export async function versionInfo() { |
||||
if ( |
||||
!versionCache.lastFetched || |
||||
(versionCache.lastFetched && |
||||
versionCache.lastFetched < Date.now() - 1000 * 60 * 60) |
||||
) { |
||||
const nonBetaTags = await axios |
||||
.get('https://api.github.com/repos/nocodb/nocodb/tags', { |
||||
timeout: 5000, |
||||
}) |
||||
.then((response) => { |
||||
return response.data |
||||
.map((x) => x.name) |
||||
.filter( |
||||
(v) => validate(v) && !v.includes('finn') && !v.includes('beta') |
||||
) |
||||
.sort((x, y) => compareVersions(y, x)); |
||||
}) |
||||
.catch(() => null); |
||||
if (nonBetaTags && nonBetaTags.length > 0) { |
||||
versionCache.releaseVersion = nonBetaTags[0]; |
||||
} |
||||
versionCache.lastFetched = Date.now(); |
||||
} |
||||
|
||||
const response = { |
||||
currentVersion: packageVersion, |
||||
releaseVersion: versionCache.releaseVersion, |
||||
}; |
||||
|
||||
return response; |
||||
} |
||||
|
||||
export async function appHealth() { |
||||
return { |
||||
message: 'OK', |
||||
timestamp: Date.now(), |
||||
uptime: process.uptime(), |
||||
}; |
||||
} |
||||
|
||||
async function _axiosRequestMake(param: { |
||||
body: { |
||||
apiMeta: any; |
||||
}; |
||||
}) { |
||||
const { apiMeta } = param.body; |
||||
|
||||
if (apiMeta?.body) { |
||||
try { |
||||
apiMeta.body = JSON.parse(apiMeta.body); |
||||
} catch (e) { |
||||
console.log(e); |
||||
} |
||||
} |
||||
|
||||
if (apiMeta?.auth) { |
||||
try { |
||||
apiMeta.auth = JSON.parse(apiMeta.auth); |
||||
} catch (e) { |
||||
console.log(e); |
||||
} |
||||
} |
||||
|
||||
apiMeta.response = {}; |
||||
const _req = { |
||||
params: apiMeta.parameters |
||||
? apiMeta.parameters.reduce((paramsObj, param) => { |
||||
if (param.name && param.enabled) { |
||||
paramsObj[param.name] = param.value; |
||||
} |
||||
return paramsObj; |
||||
}, {}) |
||||
: {}, |
||||
url: apiMeta.url, |
||||
method: apiMeta.method || 'GET', |
||||
data: apiMeta.body || {}, |
||||
headers: apiMeta.headers |
||||
? apiMeta.headers.reduce((headersObj, header) => { |
||||
if (header.name && header.enabled) { |
||||
headersObj[header.name] = header.value; |
||||
} |
||||
return headersObj; |
||||
}, {}) |
||||
: {}, |
||||
responseType: apiMeta.responseType || 'json', |
||||
withCredentials: true, |
||||
}; |
||||
const data = await require('axios')(_req); |
||||
return data?.data; |
||||
} |
||||
|
||||
export async function axiosRequestMake(param: { |
||||
body: { |
||||
apiMeta: any; |
||||
}; |
||||
}) { |
||||
const { |
||||
apiMeta: { url }, |
||||
} = param.body; |
||||
const isExcelImport = /.*\.(xls|xlsx|xlsm|ods|ots)/; |
||||
const isCSVImport = /.*\.(csv)/; |
||||
const ipBlockList = |
||||
/(10)(\.([2]([0-5][0-5]|[01234][6-9])|[1][0-9][0-9]|[1-9][0-9]|[0-9])){3}|(172)\.(1[6-9]|2[0-9]|3[0-1])(\.(2[0-4][0-9]|25[0-5]|[1][0-9][0-9]|[1-9][0-9]|[0-9])){2}|(192)\.(168)(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){2}|(0.0.0.0)|localhost?/g; |
||||
if ( |
||||
ipBlockList.test(url) || |
||||
(!isCSVImport.test(url) && !isExcelImport.test(url)) |
||||
) { |
||||
return {}; |
||||
} |
||||
if (isCSVImport || isExcelImport) { |
||||
param.body.apiMeta.responseType = 'arraybuffer'; |
||||
} |
||||
return await _axiosRequestMake({ |
||||
body: param.body, |
||||
}); |
||||
} |
||||
|
||||
export async function urlToDbConfig(param: { |
||||
body: { |
||||
url: string; |
||||
}; |
||||
}) { |
||||
const { url } = param.body; |
||||
try { |
||||
const connectionConfig = NcConfigFactory.extractXcUrlFromJdbc(url, true); |
||||
return connectionConfig; |
||||
} catch (error) { |
||||
return NcError.internalServerError(); |
||||
} |
||||
} |
||||
|
||||
interface ViewCount { |
||||
formCount: number | null; |
||||
gridCount: number | null; |
||||
galleryCount: number | null; |
||||
kanbanCount: number | null; |
||||
total: number | null; |
||||
sharedFormCount: number | null; |
||||
sharedGridCount: number | null; |
||||
sharedGalleryCount: number | null; |
||||
sharedKanbanCount: number | null; |
||||
sharedTotal: number | null; |
||||
sharedLockedCount: number | null; |
||||
} |
||||
|
||||
interface AllMeta { |
||||
projectCount: number; |
||||
projects: ( |
||||
| { |
||||
external?: boolean | null; |
||||
tableCount: { |
||||
table: number; |
||||
view: number; |
||||
} | null; |
||||
viewCount: ViewCount; |
||||
webhookCount: number | null; |
||||
filterCount: number | null; |
||||
sortCount: number | null; |
||||
rowCount: ({ totalRecords: number } | null)[] | null; |
||||
userCount: number | null; |
||||
} |
||||
| { error: string } |
||||
)[]; |
||||
userCount: number; |
||||
sharedBaseCount: number; |
||||
} |
||||
|
||||
export async function aggregatedMetaInfo() { |
||||
const [projects, userCount] = await Promise.all([ |
||||
Project.list({}), |
||||
Noco.ncMeta.metaCount(null, null, MetaTable.USERS), |
||||
]); |
||||
|
||||
const result: AllMeta = { |
||||
projectCount: projects.length, |
||||
projects: [], |
||||
userCount, |
||||
sharedBaseCount: 0, |
||||
}; |
||||
|
||||
result.projects.push( |
||||
...extractResultOrNull( |
||||
await Promise.allSettled( |
||||
projects.map(async (project) => { |
||||
if (project.uuid) result.sharedBaseCount++; |
||||
const [ |
||||
tableCount, |
||||
dbViewCount, |
||||
viewCount, |
||||
webhookCount, |
||||
filterCount, |
||||
sortCount, |
||||
rowCount, |
||||
userCount, |
||||
] = extractResultOrNull( |
||||
await Promise.allSettled([ |
||||
// db tables count
|
||||
Noco.ncMeta.metaCount(project.id, null, MetaTable.MODELS, { |
||||
condition: { |
||||
type: 'table', |
||||
}, |
||||
}), |
||||
// db views count
|
||||
Noco.ncMeta.metaCount(project.id, null, MetaTable.MODELS, { |
||||
condition: { |
||||
type: 'view', |
||||
}, |
||||
}), |
||||
// views count
|
||||
(async () => { |
||||
const views = await Noco.ncMeta.metaList2( |
||||
project.id, |
||||
null, |
||||
MetaTable.VIEWS |
||||
); |
||||
// grid, form, gallery, kanban and shared count
|
||||
return views.reduce<ViewCount>( |
||||
(out, view) => { |
||||
out.total++; |
||||
|
||||
switch (view.type) { |
||||
case ViewTypes.GRID: |
||||
out.gridCount++; |
||||
if (view.uuid) out.sharedGridCount++; |
||||
break; |
||||
case ViewTypes.FORM: |
||||
out.formCount++; |
||||
if (view.uuid) out.sharedFormCount++; |
||||
break; |
||||
case ViewTypes.GALLERY: |
||||
out.galleryCount++; |
||||
if (view.uuid) out.sharedGalleryCount++; |
||||
break; |
||||
case ViewTypes.KANBAN: |
||||
out.kanbanCount++; |
||||
if (view.uuid) out.sharedKanbanCount++; |
||||
} |
||||
|
||||
if (view.uuid) { |
||||
if (view.password) out.sharedLockedCount++; |
||||
out.sharedTotal++; |
||||
} |
||||
|
||||
return out; |
||||
}, |
||||
{ |
||||
formCount: 0, |
||||
gridCount: 0, |
||||
galleryCount: 0, |
||||
kanbanCount: 0, |
||||
total: 0, |
||||
sharedFormCount: 0, |
||||
sharedGridCount: 0, |
||||
sharedGalleryCount: 0, |
||||
sharedKanbanCount: 0, |
||||
sharedTotal: 0, |
||||
sharedLockedCount: 0, |
||||
} |
||||
); |
||||
})(), |
||||
// webhooks count
|
||||
Noco.ncMeta.metaCount(project.id, null, MetaTable.HOOKS), |
||||
// filters count
|
||||
Noco.ncMeta.metaCount(project.id, null, MetaTable.FILTER_EXP), |
||||
// sorts count
|
||||
Noco.ncMeta.metaCount(project.id, null, MetaTable.SORT), |
||||
// row count per base
|
||||
project.getBases().then(async (bases) => { |
||||
return extractResultOrNull( |
||||
await Promise.allSettled( |
||||
bases.map(async (base) => |
||||
(await NcConnectionMgrv2.getSqlClient(base)) |
||||
.totalRecords?.() |
||||
?.then((result) => result?.data) |
||||
) |
||||
) |
||||
); |
||||
}), |
||||
// project users count
|
||||
Noco.ncMeta.metaCount(null, null, MetaTable.PROJECT_USERS, { |
||||
condition: { |
||||
project_id: project.id, |
||||
}, |
||||
aggField: '*', |
||||
}), |
||||
]) |
||||
); |
||||
|
||||
return { |
||||
tableCount: { table: tableCount, view: dbViewCount }, |
||||
external: !project.is_meta, |
||||
viewCount, |
||||
webhookCount, |
||||
filterCount, |
||||
sortCount, |
||||
rowCount, |
||||
userCount, |
||||
}; |
||||
}) |
||||
) |
||||
) |
||||
); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
const extractResultOrNull = (results: PromiseSettledResult<any>[]) => { |
||||
return results.map((result) => { |
||||
if (result.status === 'fulfilled') { |
||||
return result.value; |
||||
} |
||||
console.log(result.reason); |
||||
return null; |
||||
}); |
||||
}; |
Loading…
Reference in new issue