Browse Source

feat: baseApis

Signed-off-by: mertmit <mertmit99@gmail.com>
pull/3573/head
mertmit 2 years ago
parent
commit
b795694c1b
  1. 125
      packages/nocodb-sdk/src/lib/Api.ts
  2. 367
      packages/nocodb/src/lib/meta/api/baseApis.ts
  3. 2
      packages/nocodb/src/lib/meta/api/index.ts
  4. 12
      packages/nocodb/src/lib/meta/api/tableApis.ts
  5. 2
      packages/nocodb/src/lib/models/Base.ts
  6. 225
      scripts/sdk/swagger.json

125
packages/nocodb-sdk/src/lib/Api.ts

@ -106,8 +106,8 @@ export interface BaseListType {
export interface TableType {
id?: string;
fk_project_id?: string;
fk_base_id?: string;
project_id?: string;
base_id?: string;
table_name: string;
title: string;
type?: string;
@ -121,7 +121,6 @@ export interface TableType {
columns?: ColumnType[];
columnsById?: object;
slug?: string;
project_id?: string;
mm?: boolean | number;
}
@ -1892,6 +1891,126 @@ export class Api<
...params,
}),
};
base = {
/**
* @description Read project base details
*
* @tags Base
* @name Read
* @summary Base read
* @request GET:/api/v1/db/meta/projects/{projectId}/bases/{baseId}
* @response `200` `object` OK
*/
read: (projectId: string, baseId: string, params: RequestParams = {}) =>
this.request<object, any>({
path: `/api/v1/db/meta/projects/${projectId}/bases/${baseId}`,
method: 'GET',
format: 'json',
...params,
}),
/**
* No description
*
* @tags Base
* @name Delete
* @summary Base delete
* @request DELETE:/api/v1/db/meta/projects/{projectId}/bases/{baseId}
* @response `200` `void` OK
*/
delete: (projectId: string, baseId: string, params: RequestParams = {}) =>
this.request<void, any>({
path: `/api/v1/db/meta/projects/${projectId}/bases/${baseId}`,
method: 'DELETE',
...params,
}),
/**
* No description
*
* @tags Base
* @name Update
* @summary Base update
* @request PATCH:/api/v1/db/meta/projects/{projectId}/bases/{baseId}
* @response `200` `void` OK
*/
update: (
projectId: string,
baseId: string,
data: any,
params: RequestParams = {}
) =>
this.request<void, any>({
path: `/api/v1/db/meta/projects/${projectId}/bases/${baseId}`,
method: 'PATCH',
body: data,
type: ContentType.Json,
...params,
}),
/**
* @description Get project base list
*
* @tags Base
* @name List
* @summary Base list
* @request GET:/api/v1/db/meta/projects/{projectId}/bases/
* @response `200` `object` OK
*/
list: (projectId: string, params: RequestParams = {}) =>
this.request<object, any>({
path: `/api/v1/db/meta/projects/${projectId}/bases/`,
method: 'GET',
format: 'json',
...params,
}),
/**
* No description
*
* @tags Base
* @name Create
* @summary Base create
* @request POST:/api/v1/db/meta/projects/{projectId}/bases/
* @response `200` `BaseType` OK
*/
create: (
projectId: string,
data: BaseType & { external?: boolean },
params: RequestParams = {}
) =>
this.request<BaseType, any>({
path: `/api/v1/db/meta/projects/${projectId}/bases/`,
method: 'POST',
body: data,
type: ContentType.Json,
format: 'json',
...params,
}),
/**
* No description
*
* @tags Base
* @name TableCreate
* @request POST:/api/v1/db/meta/projects/{projectId}/{baseId}/tables
* @response `200` `TableType` OK
*/
tableCreate: (
projectId: string,
baseId: string,
data: TableReqType,
params: RequestParams = {}
) =>
this.request<TableType, any>({
path: `/api/v1/db/meta/projects/${projectId}/${baseId}/tables`,
method: 'POST',
body: data,
type: ContentType.Json,
format: 'json',
...params,
}),
};
dbTable = {
/**
* No description

367
packages/nocodb/src/lib/meta/api/baseApis.ts

@ -0,0 +1,367 @@
import { Request, Response } from 'express';
import Project from '../../models/Project';
import { ModelTypes, ProjectListType, UITypes } from 'nocodb-sdk';
import { PagedResponseImpl } from '../helpers/PagedResponse';
import syncMigration from '../helpers/syncMigration';
import { IGNORE_TABLES } from '../../utils/common/BaseApiBuilder';
import Column from '../../models/Column';
import Model from '../../models/Model';
import NcHelp from '../../utils/NcHelp';
import Base from '../../models/Base';
import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
import getTableNameAlias, { getColumnNameAlias } from '../helpers/getTableName';
import LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn';
import ncMetaAclMw from '../helpers/ncMetaAclMw';
import { Tele } from 'nc-help';
import { NcError } from '../helpers/catchError';
import getColumnUiType from '../helpers/getColumnUiType';
import mapDefaultPrimaryValue from '../helpers/mapDefaultPrimaryValue';
import { extractAndGenerateManyToManyRelations } from './metaDiffApis';
import { metaApiMetrics } from '../helpers/apiMetrics';
export async function baseGet(
req: Request<any, any, any>,
res: Response<Base>
) {
const base = await Base.get(req.params.baseId);
delete base.config;
res.json(base);
}
export async function baseUpdate(
_req: Request<any, any, any>,
_res: Response<ProjectListType>
) {
NcError.badRequest('Base update not yet supported');
}
export async function baseList(
req: Request<any, any, any>,
res: Response<ProjectListType>,
next
) {
try {
const projects = await Project.list(req.query);
res // todo: pagination
.json(
new PagedResponseImpl(projects, {
count: projects.length,
limit: projects.length,
})
);
} catch (e) {
console.log(e);
next(e);
}
}
export async function baseDelete(
req: Request<any, any, any>,
res: Response<any>
) {
const base = await Base.get(req.params.baseId);
const result = await base.delete();
Tele.emit('evt', { evt_type: 'base:deleted' });
res.json(result);
}
async function baseCreate(req: Request<any, any>, res) {
// type | base | projectId
const baseBody = req.body;
const base = await Base.createBase(baseBody);
const project = await base.getProject();
await syncMigration(project);
const info = await populateMeta(base, project);
Tele.emit('evt_api_created', info);
delete base.config;
Tele.emit('evt', {
evt_type: 'base:created'
});
res.json(base);
}
async function populateMeta(base: Base, project: Project): Promise<any> {
const info = {
type: 'rest',
apiCount: 0,
tablesCount: 0,
relationsCount: 0,
viewsCount: 0,
client: base?.getConnectionConfig()?.client,
timeTaken: 0,
};
const t = process.hrtime();
const sqlClient = NcConnectionMgrv2.getSqlClient(base);
let order = 1;
const models2: { [tableName: string]: Model } = {};
const virtualColumnsInsert = [];
/* Get all relations */
const relations = (await sqlClient.relationListAll())?.data?.list;
info.relationsCount = relations.length;
let tables = (await sqlClient.tableList())?.data?.list
?.filter(({ tn }) => !IGNORE_TABLES.includes(tn))
?.map((t) => {
t.order = ++order;
t.title = getTableNameAlias(t.tn, project.prefix, base);
t.table_name = t.tn;
return t;
});
/* filter based on prefix */
if (project?.prefix) {
tables = tables.filter((t) => {
return t?.tn?.startsWith(project?.prefix);
});
}
info.tablesCount = tables.length;
tables.forEach((t) => {
t.title = getTableNameAlias(t.tn, project.prefix, base);
});
relations.forEach((r) => {
r.title = getTableNameAlias(r.tn, project.prefix, base);
r.rtitle = getTableNameAlias(r.rtn, project.prefix, base);
});
// await this.syncRelations();
const tableMetasInsert = tables.map((table) => {
return async () => {
/* filter relation where this table is present */
const tableRelations = relations.filter(
(r) => r.tn === table.tn || r.rtn === table.tn
);
const columns: Array<
Omit<Column, 'column_name' | 'title'> & {
cn: string;
system?: boolean;
}
> = (await sqlClient.columnList({ tn: table.tn }))?.data?.list;
const hasMany =
table.type === 'view'
? []
: tableRelations.filter((r) => r.rtn === table.tn);
const belongsTo =
table.type === 'view'
? []
: tableRelations.filter((r) => r.tn === table.tn);
mapDefaultPrimaryValue(columns);
// add vitual columns
const virtualColumns = [
...hasMany.map((hm) => {
return {
uidt: UITypes.LinkToAnotherRecord,
type: 'hm',
hm,
title: `${hm.title} List`,
};
}),
...belongsTo.map((bt) => {
// find and mark foreign key column
const fkColumn = columns.find((c) => c.cn === bt.cn);
if (fkColumn) {
fkColumn.uidt = UITypes.ForeignKey;
fkColumn.system = true;
}
return {
uidt: UITypes.LinkToAnotherRecord,
type: 'bt',
bt,
title: `${bt.rtitle}`,
};
}),
];
// await Model.insert(project.id, base.id, meta);
/* create nc_models and its rows if it doesn't exists */
models2[table.table_name] = await Model.insert(project.id, base.id, {
table_name: table.tn || table.table_name,
title: table.title,
type: table.type || 'table',
order: table.order,
});
// table crud apis
info.apiCount += 5;
let colOrder = 1;
for (const column of columns) {
await Column.insert({
uidt: column.uidt || getColumnUiType(base, column),
fk_model_id: models2[table.tn].id,
...column,
title: getColumnNameAlias(column.cn, base),
column_name: column.cn,
order: colOrder++,
});
}
virtualColumnsInsert.push(async () => {
const columnNames = {};
for (const column of virtualColumns) {
// generate unique name if there is any duplicate column name
let c = 0;
while (`${column.title}${c || ''}` in columnNames) {
c++;
}
column.title = `${column.title}${c || ''}`;
columnNames[column.title] = true;
const rel = column.hm || column.bt;
const rel_column_id = (await models2?.[rel.tn]?.getColumns())?.find(
(c) => c.column_name === rel.cn
)?.id;
const tnId = models2?.[rel.tn]?.id;
const ref_rel_column_id = (
await models2?.[rel.rtn]?.getColumns()
)?.find((c) => c.column_name === rel.rcn)?.id;
const rtnId = models2?.[rel.rtn]?.id;
try {
await Column.insert<LinkToAnotherRecordColumn>({
project_id: project.id,
db_alias: base.id,
fk_model_id: models2[table.tn].id,
cn: column.cn,
title: column.title,
uidt: column.uidt,
type: column.hm ? 'hm' : column.mm ? 'mm' : 'bt',
// column_id,
fk_child_column_id: rel_column_id,
fk_parent_column_id: ref_rel_column_id,
fk_index_name: rel.fkn,
ur: rel.ur,
dr: rel.dr,
order: colOrder++,
fk_related_model_id: column.hm ? tnId : rtnId,
system: column.system,
});
// nested relations data apis
info.apiCount += 5;
} catch (e) {
console.log(e);
}
}
});
};
});
/* handle xc_tables update in parallel */
await NcHelp.executeOperations(tableMetasInsert, base.type);
await NcHelp.executeOperations(virtualColumnsInsert, base.type);
await extractAndGenerateManyToManyRelations(Object.values(models2));
let views: Array<{ order: number; table_name: string; title: string }> = (
await sqlClient.viewList()
)?.data?.list
// ?.filter(({ tn }) => !IGNORE_TABLES.includes(tn))
?.map((v) => {
v.order = ++order;
v.table_name = v.view_name;
v.title = getTableNameAlias(v.view_name, project.prefix, base);
return v;
});
/* filter based on prefix */
if (project?.prefix) {
views = tables.filter((t) => {
return t?.tn?.startsWith(project?.prefix);
});
}
info.viewsCount = views.length;
const viewMetasInsert = views.map((table) => {
return async () => {
const columns = (await sqlClient.columnList({ tn: table.table_name }))
?.data?.list;
/* create nc_models and its rows if it doesn't exists */
models2[table.table_name] = await Model.insert(project.id, base.id, {
table_name: table.table_name,
title: getTableNameAlias(table.table_name, project.prefix, base),
// todo: sanitize
type: ModelTypes.VIEW,
order: table.order,
});
let colOrder = 1;
// view apis
info.apiCount += 2;
for (const column of columns) {
await Column.insert({
fk_model_id: models2[table.table_name].id,
...column,
title: getColumnNameAlias(column.cn, base),
order: colOrder++,
uidt: getColumnUiType(base, column),
});
}
};
});
await NcHelp.executeOperations(viewMetasInsert, base.type);
const t1 = process.hrtime(t);
const t2 = t1[0] + t1[1] / 1000000000;
(info as any).timeTaken = t2.toFixed(1);
return info;
}
export default (router) => {
router.get(
'/api/v1/db/meta/projects/:projectId/bases/:baseId',
metaApiMetrics,
ncMetaAclMw(baseGet, 'baseGet')
);
router.patch(
'/api/v1/db/meta/projects/:projectId/bases/:baseId',
metaApiMetrics,
ncMetaAclMw(baseUpdate, 'baseUpdate')
);
router.delete(
'/api/v1/db/meta/projects/:projectId/bases/:baseId',
metaApiMetrics,
ncMetaAclMw(baseDelete, 'baseDelete')
);
router.post(
'/api/v1/db/meta/projects/:projectId/bases',
metaApiMetrics,
ncMetaAclMw(baseCreate, 'baseCreate')
);
router.get(
'/api/v1/db/meta/projects/:projectId/bases',
metaApiMetrics,
ncMetaAclMw(baseList, 'baseList')
);
};

2
packages/nocodb/src/lib/meta/api/index.ts

@ -3,6 +3,7 @@ import orgLicenseApis from './orgLicenseApis';
import orgTokenApis from './orgTokenApis';
import orgUserApis from './orgUserApis';
import projectApis from './projectApis';
import baseApis from './baseApis';
import tableApis from './tableApis';
import columnApis from './columnApis';
import { Router } from 'express';
@ -60,6 +61,7 @@ const jobs: { [id: string]: { last_message: any } } = {};
export default function (router: Router, server) {
initStrategies(router);
projectApis(router);
baseApis(router);
utilApis(router);
if (process.env['PLAYWRIGHT_TEST'] === 'true') {

12
packages/nocodb/src/lib/meta/api/tableApis.ts

@ -89,7 +89,10 @@ export async function tableList(req: Request, res: Response<TableListType>) {
export async function tableCreate(req: Request<any, any, TableReqType>, res) {
const project = await Project.getWithInfo(req.params.projectId);
const base = project.bases[0];
let base = project.bases[0];
if (req.params.baseId) {
base = project.bases.find((b) => b.id === req.params.baseId);
}
if (
!req.body.table_name ||
@ -224,7 +227,7 @@ export async function tableUpdate(req: Request<any, any>, res) {
const model = await Model.get(req.params.tableId);
const project = await Project.getWithInfo(req.body.project_id);
const base = project.bases[0];
const base = project.bases.find((b) => b.id === model.base_id);
if (!req.body.table_name) {
NcError.badRequest(
@ -376,6 +379,11 @@ router.post(
metaApiMetrics,
ncMetaAclMw(tableCreate, 'tableCreate')
);
router.post(
'/api/v1/db/meta/projects/:projectId/:baseId/tables',
metaApiMetrics,
ncMetaAclMw(tableCreate, 'tableCreate')
);
router.get(
'/api/v1/db/meta/tables/:tableId',
metaApiMetrics,

2
packages/nocodb/src/lib/models/Base.ts

@ -83,6 +83,7 @@ export default class Base implements BaseType {
return new Base(baseData);
});
}
static async get(id: string, ncMeta = Noco.ncMeta): Promise<Base> {
let baseData =
id &&
@ -123,6 +124,7 @@ export default class Base implements BaseType {
return config;
}
getProject(ncMeta = Noco.ncMeta): Promise<Project> {
return Project.get(this.project_id, ncMeta);
}

225
scripts/sdk/swagger.json

@ -1266,6 +1266,172 @@
}
}
},
"/api/v1/db/meta/projects/{projectId}/bases/{baseId}": {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "projectId",
"in": "path",
"required": true
},
{
"schema": {
"type": "string"
},
"name": "baseId",
"in": "path",
"required": true
}
],
"get": {
"summary": "Base read",
"operationId": "base-read",
"description": "Read project base details",
"parameters": [
{
"schema": {
"type": "string"
},
"in": "header",
"name": "xc-auth",
"description": "Auth token"
}
],
"tags": [
"Base"
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
}
}
}
},
"delete": {
"summary": "Base delete",
"operationId": "base-delete",
"responses": {
"200": {
"description": "OK"
}
},
"tags": [
"Base"
]
},
"patch": {
"summary": "Base update",
"operationId": "base-update",
"responses": {
"200": {
"description": "OK"
}
},
"tags": [
"Base"
],
"requestBody": {
"content": {
"application/json": {
"schema": {}
}
}
}
}
},
"/api/v1/db/meta/projects/{projectId}/bases/": {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "projectId",
"in": "path",
"required": true
}
],
"get": {
"summary": "Base list",
"operationId": "base-list",
"description": "Get project base list",
"parameters": [
{
"schema": {
"type": "string"
},
"in": "header",
"name": "xc-auth",
"description": "Auth token"
}
],
"tags": [
"Base"
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {}
}
}
}
}
}
},
"post": {
"summary": "Base create",
"operationId": "base-create",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Base"
}
}
}
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/Base"
},
{
"type": "object",
"properties": {
"external": {
"type": "boolean",
"default": false
}
}
}
]
}
}
}
},
"tags": [
"Base"
]
}
},
"/api/v1/db/meta/projects/{projectId}/shared": {
"parameters": [
{
@ -1558,6 +1724,54 @@
]
}
},
"/api/v1/db/meta/projects/{projectId}/{baseId}/tables": {
"parameters": [
{
"schema": {
"type": "string"
},
"name": "projectId",
"in": "path",
"required": true
},
{
"schema": {
"type": "string"
},
"name": "baseId",
"in": "path",
"required": true
}
],
"post": {
"summary": "",
"operationId": "table-create",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Table"
}
}
}
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TableReq"
}
}
}
},
"tags": [
"Base"
]
}
},
"/api/v1/db/meta/tables/{tableId}": {
"parameters": [
{
@ -7040,8 +7254,8 @@
"examples": [
{
"id": "string",
"fk_project_id": "string",
"fk_base_id": "string",
"project_id": "string",
"base_id": "string",
"title": "string",
"alias": "string",
"type": "string",
@ -7107,10 +7321,10 @@
"id": {
"type": "string"
},
"fk_project_id": {
"project_id": {
"type": "string"
},
"fk_base_id": {
"base_id": {
"type": "string"
},
"table_name": {
@ -7155,9 +7369,6 @@
"slug": {
"type": "string"
},
"project_id": {
"type": "string"
},
"mm": {
"type": [
"boolean",

Loading…
Cancel
Save