Browse Source

refactor: public apis

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/5239/head
Pranav C 2 years ago
parent
commit
92d479386d
  1. 5
      packages/nocodb/src/lib/controllers/public/index.ts
  2. 104
      packages/nocodb/src/lib/controllers/public/publicDataApis.ts
  3. 266
      packages/nocodb/src/lib/controllers/public/publicDataExportApis.ts
  4. 31
      packages/nocodb/src/lib/controllers/public/publicMetaApis.ts
  5. 1
      packages/nocodb/src/lib/services/index.ts
  6. 5
      packages/nocodb/src/lib/services/public/index.ts
  7. 162
      packages/nocodb/src/lib/services/public/publicDataExportService.ts
  8. 463
      packages/nocodb/src/lib/services/public/publicDataService.ts
  9. 100
      packages/nocodb/src/lib/services/public/publicMetaservice.ts

5
packages/nocodb/src/lib/controllers/public/index.ts

@ -0,0 +1,5 @@
import publicDataApis from './publicDataApis';
import publicDataExportApis from './publicDataExportApis';
import publicMetaApis from './publicMetaApis';
export { publicDataApis, publicDataExportApis, publicMetaApis };

104
packages/nocodb/src/lib/controllers/public/publicDataApis.ts

@ -0,0 +1,104 @@
import { Request, Response, Router } from 'express';
import multer from 'multer';
import { NC_ATTACHMENT_FIELD_SIZE } from '../../constants';
import catchError from '../../meta/helpers/catchError';
import { publicDataService } from '../../services';
export async function dataList(req: Request, res: Response) {
const pagedResponse = publicDataService.dataList({
query: req.query,
password: req.headers?.['xc-password'] as string,
sharedViewUuid: req.params.sharedViewUuid,
});
res.json(pagedResponse);
}
// todo: Handle the error case where view doesnt belong to model
async function groupedDataList(req: Request, res: Response) {
const groupedData = await publicDataService.groupedDataList({
query: req.query,
password: req.headers?.['xc-password'] as string,
sharedViewUuid: req.params.sharedViewUuid,
groupColumnId: req.params.columnId,
});
res.json(groupedData);
}
async function dataInsert(req: Request & { files: any[] }, res: Response) {
const insertResult = await publicDataService.dataInsert({
sharedViewUuid: req.params.sharedViewUuid,
password: req.headers?.['xc-password'] as string,
body: req.body?.data,
siteUrl: (req as any).ncSiteUrl,
files: req.files,
});
res.json(insertResult);
}
async function relDataList(req, res) {
const pagedResponse = publicDataService.relDataList({
query: req.query,
password: req.headers?.['xc-password'] as string,
sharedViewUuid: req.params.sharedViewUuid,
columnId: req.params.columnId,
});
res.json(pagedResponse);
}
export async function publicMmList(req: Request, res: Response) {
const paginatedResponse = await publicDataService.publicMmList({
query: req.query,
password: req.headers?.['xc-password'] as string,
sharedViewUuid: req.params.sharedViewUuid,
columnId: req.params.columnId,
rowId: req.params.rowId,
});
res.json(paginatedResponse);
}
export async function publicHmList(req: Request, res: Response) {
const paginatedResponse = await publicDataService.publicHmList({
query: req.query,
password: req.headers?.['xc-password'] as string,
sharedViewUuid: req.params.sharedViewUuid,
columnId: req.params.columnId,
rowId: req.params.rowId,
});
res.json(paginatedResponse);
}
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/public/shared-view/:sharedViewUuid/rows',
catchError(dataList)
);
router.get(
'/api/v1/db/public/shared-view/:sharedViewUuid/group/:columnId',
catchError(groupedDataList)
);
router.get(
'/api/v1/db/public/shared-view/:sharedViewUuid/nested/:columnId',
catchError(relDataList)
);
router.post(
'/api/v1/db/public/shared-view/:sharedViewUuid/rows',
multer({
storage: multer.diskStorage({}),
limits: {
fieldSize: NC_ATTACHMENT_FIELD_SIZE,
},
}).any(),
catchError(dataInsert)
);
router.get(
'/api/v1/db/public/shared-view/:sharedViewUuid/rows/:rowId/mm/:colId',
catchError(publicMmList)
);
router.get(
'/api/v1/db/public/shared-view/:sharedViewUuid/rows/:rowId/hm/:colId',
catchError(publicHmList)
);
export default router;

266
packages/nocodb/src/lib/controllers/public/publicDataExportApis.ts

@ -0,0 +1,266 @@
import { Request, Response, Router } from 'express';
import * as XLSX from 'xlsx';
import { nocoExecute } from 'nc-help';
import papaparse from 'papaparse';
import { ErrorMessages, isSystemColumn, UITypes, ViewTypes } from 'nocodb-sdk';
import getAst from '../../db/sql-data-mapper/lib/sql/helpers/getAst';
import catchError, { NcError } from '../../meta/helpers/catchError';
import {
Base,
Column,
LinkToAnotherRecordColumn,
LookupColumn,
Model,
View,
} from '../../models';
import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
async function exportExcel(req: Request, res: Response) {
const view = await View.getByUUID(req.params.publicDataUuid);
if (!view) NcError.notFound('Not found');
if (
view.type !== ViewTypes.GRID &&
view.type !== ViewTypes.KANBAN &&
view.type !== ViewTypes.GALLERY &&
view.type !== ViewTypes.MAP
)
NcError.notFound('Not found');
if (view.password && view.password !== req.headers?.['xc-password']) {
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
const model = await view.getModelWithInfo();
await view.getColumns();
const { offset, dbRows, elapsed } = await getDbRows(model, view, req);
const fields = req.query.fields as string[];
const data = XLSX.utils.json_to_sheet(
dbRows.map((o: Record<string, any>) =>
Object.fromEntries(fields.map((f) => [f, o[f]]))
),
{ header: fields }
);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, data, view.title);
const buf = XLSX.write(wb, { type: 'base64', bookType: 'xlsx' });
res.set({
'Access-Control-Expose-Headers': 'nc-export-offset',
'nc-export-offset': offset,
'nc-export-elapsed-time': elapsed,
'Content-Disposition': `attachment; filename="${encodeURI(
view.title
)}-export.xlsx"`,
});
res.end(buf);
}
async function exportCsv(req: Request, res: Response) {
const view = await View.getByUUID(req.params.publicDataUuid);
const fields = req.query.fields;
if (!view) NcError.notFound('Not found');
if (
view.type !== ViewTypes.GRID &&
view.type !== ViewTypes.KANBAN &&
view.type !== ViewTypes.GALLERY &&
view.type !== ViewTypes.MAP
)
NcError.notFound('Not found');
if (view.password && view.password !== req.headers?.['xc-password']) {
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
const model = await view.getModelWithInfo();
await view.getColumns();
const { offset, dbRows, elapsed } = await getDbRows(model, view, req);
const data = papaparse.unparse(
{
fields: model.columns
.sort((c1, c2) =>
Array.isArray(fields)
? fields.indexOf(c1.title as any) - fields.indexOf(c2.title as any)
: 0
)
.filter(
(c) =>
!fields || !Array.isArray(fields) || fields.includes(c.title as any)
)
.map((c) => c.title),
data: dbRows,
},
{
escapeFormulae: true,
}
);
res.set({
'Access-Control-Expose-Headers': 'nc-export-offset',
'nc-export-offset': offset,
'nc-export-elapsed-time': elapsed,
'Content-Disposition': `attachment; filename="${encodeURI(
view.title
)}-export.csv"`,
});
res.send(data);
}
async function getDbRows(model, view: View, req: Request) {
view.model.columns = view.columns
.filter((c) => c.show)
.map(
(c) =>
new Column({ ...c, ...view.model.columnsById[c.fk_column_id] } as any)
)
.filter((column) => !isSystemColumn(column) || view.show_system_fields);
if (!model) NcError.notFound('Table not found');
const listArgs: any = { ...req.query };
try {
listArgs.filterArr = JSON.parse(listArgs.filterArrJson);
} catch (e) {}
try {
listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
} catch (e) {}
const base = await Base.get(model.base_id);
const baseModel = await Model.getBaseModelSQL({
id: model.id,
viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base),
});
const requestObj = await getAst({
query: req.query,
model,
view,
includePkByDefault: false,
});
let offset = +req.query.offset || 0;
const limit = 100;
// const size = +process.env.NC_EXPORT_MAX_SIZE || 1024;
const timeout = +process.env.NC_EXPORT_MAX_TIMEOUT || 5000;
const dbRows = [];
const startTime = process.hrtime();
let elapsed, temp;
for (
elapsed = 0;
elapsed < timeout;
offset += limit,
temp = process.hrtime(startTime),
elapsed = temp[0] * 1000 + temp[1] / 1000000
) {
const rows = await nocoExecute(
requestObj,
await baseModel.list({ ...listArgs, offset, limit }),
{},
listArgs
);
if (!rows?.length) {
offset = -1;
break;
}
for (const row of rows) {
const dbRow = { ...row };
for (const column of view.model.columns) {
dbRow[column.title] = await serializeCellValue({
value: row[column.title],
column,
});
}
dbRows.push(dbRow);
}
}
return { offset, dbRows, elapsed };
}
async function serializeCellValue({
value,
column,
}: {
column?: Column;
value: any;
}) {
if (!column) {
return value;
}
if (!value) return value;
switch (column?.uidt) {
case UITypes.Attachment: {
let data = value;
try {
if (typeof value === 'string') {
data = JSON.parse(value);
}
} catch {}
return (data || []).map(
(attachment) =>
`${encodeURI(attachment.title)}(${encodeURI(attachment.url)})`
);
}
case UITypes.Lookup:
{
const colOptions = await column.getColOptions<LookupColumn>();
const lookupColumn = await colOptions.getLookupColumn();
return (
await Promise.all(
[...(Array.isArray(value) ? value : [value])].map(async (v) =>
serializeCellValue({
value: v,
column: lookupColumn,
})
)
)
).join(', ');
}
break;
case UITypes.LinkToAnotherRecord:
{
const colOptions =
await column.getColOptions<LinkToAnotherRecordColumn>();
const relatedModel = await colOptions.getRelatedTable();
await relatedModel.getColumns();
return [...(Array.isArray(value) ? value : [value])]
.map((v) => {
return v[relatedModel.displayValue?.title];
})
.join(', ');
}
break;
default:
if (value && typeof value === 'object') {
return JSON.stringify(value);
}
return value;
}
}
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/public/shared-view/:publicDataUuid/rows/export/csv',
catchError(exportCsv)
);
router.get(
'/api/v1/db/public/shared-view/:publicDataUuid/rows/export/excel',
catchError(exportExcel)
);
export default router;

31
packages/nocodb/src/lib/controllers/public/publicMetaApis.ts

@ -0,0 +1,31 @@
import { Request, Response, Router } from 'express';
import catchError from '../../meta/helpers/catchError';
import { publicMetaService } from '../../services';
export async function viewMetaGet(req: Request, res: Response) {
res.json(
await publicMetaService.viewMetaGet({
password: req.headers?.['xc-password'] as string,
sharedViewUuid: req.params.sharedViewUuid,
})
);
}
async function publicSharedBaseGet(req, res): Promise<any> {
res.json(
await publicMetaService.publicSharedBaseGet({
sharedBaseUuid: req.params.sharedViewUuid,
})
);
}
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/public/shared-view/:sharedViewUuid/meta',
catchError(viewMetaGet)
);
router.get(
'/api/v1/db/public/shared-base/:sharedBaseUuid/meta',
catchError(publicSharedBaseGet)
);
export default router;

1
packages/nocodb/src/lib/services/index.ts

@ -33,3 +33,4 @@ export * as auditService from './auditService';
export * as swaggerService from './swaggerService'; export * as swaggerService from './swaggerService';
export * as userService from './userService'; export * as userService from './userService';
export * as syncService from './syncService'; export * as syncService from './syncService';
export * from './public';

5
packages/nocodb/src/lib/services/public/index.ts

@ -0,0 +1,5 @@
import * as publicDataService from './publicDataService';
import * as publicDataExportApis from './publicDataExportService';
import * as publicMetaService from './publicMetaservice';
export { publicDataService, publicDataExportApis, publicMetaService };

162
packages/nocodb/src/lib/services/public/publicDataExportService.ts

@ -0,0 +1,162 @@
import { nocoExecute } from 'nc-help';
import { isSystemColumn, UITypes } from 'nocodb-sdk';
import getAst from '../../db/sql-data-mapper/lib/sql/helpers/getAst';
import { NcError } from '../../meta/helpers/catchError';
import {
Base,
Column,
LinkToAnotherRecordColumn,
LookupColumn,
Model,
View,
} from '../../models';
import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
export async function getDbRows(param: {
model;
view: View;
query: any;
offset?: number;
}) {
param.view.model.columns = param.view.columns
.filter((c) => c.show)
.map(
(c) =>
new Column({
...c,
...param.view.model.columnsById[c.fk_column_id],
} as any)
)
.filter(
(column) => !isSystemColumn(column) || param.view.show_system_fields
);
if (!param.model) NcError.notFound('Table not found');
const listArgs: any = { ...param.query };
try {
listArgs.filterArr = JSON.parse(listArgs.filterArrJson);
} catch (e) {}
try {
listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
} catch (e) {}
const base = await Base.get(param.model.base_id);
const baseModel = await Model.getBaseModelSQL({
id: param.model.id,
viewId: param.view?.id,
dbDriver: NcConnectionMgrv2.get(base),
});
const requestObj = await getAst({
query: param.query,
model: param.model,
view: param.view,
includePkByDefault: false,
});
let offset = +param.offset || 0;
const limit = 100;
// const size = +process.env.NC_EXPORT_MAX_SIZE || 1024;
const timeout = +process.env.NC_EXPORT_MAX_TIMEOUT || 5000;
const dbRows = [];
const startTime = process.hrtime();
let elapsed, temp;
for (
elapsed = 0;
elapsed < timeout;
offset += limit,
temp = process.hrtime(startTime),
elapsed = temp[0] * 1000 + temp[1] / 1000000
) {
const rows = await nocoExecute(
requestObj,
await baseModel.list({ ...listArgs, offset, limit }),
{},
listArgs
);
if (!rows?.length) {
offset = -1;
break;
}
for (const row of rows) {
const dbRow = { ...row };
for (const column of param.view.model.columns) {
dbRow[column.title] = await serializeCellValue({
value: row[column.title],
column,
});
}
dbRows.push(dbRow);
}
}
return { offset, dbRows, elapsed };
}
export async function serializeCellValue({
value,
column,
}: {
column?: Column;
value: any;
}) {
if (!column) {
return value;
}
if (!value) return value;
switch (column?.uidt) {
case UITypes.Attachment: {
let data = value;
try {
if (typeof value === 'string') {
data = JSON.parse(value);
}
} catch {}
return (data || []).map(
(attachment) =>
`${encodeURI(attachment.title)}(${encodeURI(attachment.url)})`
);
}
case UITypes.Lookup:
{
const colOptions = await column.getColOptions<LookupColumn>();
const lookupColumn = await colOptions.getLookupColumn();
return (
await Promise.all(
[...(Array.isArray(value) ? value : [value])].map(async (v) =>
serializeCellValue({
value: v,
column: lookupColumn,
})
)
)
).join(', ');
}
break;
case UITypes.LinkToAnotherRecord:
{
const colOptions =
await column.getColOptions<LinkToAnotherRecordColumn>();
const relatedModel = await colOptions.getRelatedTable();
await relatedModel.getColumns();
return [...(Array.isArray(value) ? value : [value])]
.map((v) => {
return v[relatedModel.displayValue?.title];
})
.join(', ');
}
break;
default:
if (value && typeof value === 'object') {
return JSON.stringify(value);
}
return value;
}
}

463
packages/nocodb/src/lib/services/public/publicDataService.ts

@ -0,0 +1,463 @@
import { nocoExecute } from 'nc-help';
import { ErrorMessages, UITypes, ViewTypes } from 'nocodb-sdk';
import path from 'path';
import { nanoid } from 'nanoid';
import slash from 'slash';
import getAst from '../../db/sql-data-mapper/lib/sql/helpers/getAst';
import { NcError } from '../../meta/helpers/catchError';
import NcPluginMgrv2 from '../../meta/helpers/NcPluginMgrv2';
import { PagedResponseImpl } from '../../meta/helpers/PagedResponse';
import {
Base,
Column,
LinkToAnotherRecordColumn,
Model,
View,
} from '../../models';
import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
import { mimeIcons } from '../../utils/mimeTypes';
import { sanitizeUrlPath } from '../attachmentService';
import { getColumnByIdOrName } from '../dataService/helpers';
export async function dataList(param: {
sharedViewUuid: string;
password?: string;
query: any;
}) {
const view = await View.getByUUID(param.sharedViewUuid);
if (!view) NcError.notFound('Not found');
if (
view.type !== ViewTypes.GRID &&
view.type !== ViewTypes.KANBAN &&
view.type !== ViewTypes.GALLERY &&
view.type !== ViewTypes.MAP
) {
NcError.notFound('Not found');
}
if (view.password && view.password !== param.password) {
return NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
const model = await Model.getByIdOrName({
id: view?.fk_model_id,
});
const base = await Base.get(model.base_id);
const baseModel = await Model.getBaseModelSQL({
id: model.id,
viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base),
});
const listArgs: any = { ...param.query };
try {
listArgs.filterArr = JSON.parse(listArgs.filterArrJson);
} catch (e) {}
try {
listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
} catch (e) {}
let data = [];
let count = 0;
try {
data = await nocoExecute(
await getAst({
query: param.query,
model,
view,
}),
await baseModel.list(listArgs),
{},
listArgs
);
count = await baseModel.count(listArgs);
} catch (e) {
// show empty result instead of throwing error here
// e.g. search some text in a numeric field
}
return new PagedResponseImpl(data, { ...param.query, count });
}
// todo: Handle the error case where view doesnt belong to model
export async function groupedDataList(param: {
sharedViewUuid: string;
password?: string;
query: any;
groupColumnId: string;
}) {
const view = await View.getByUUID(param.sharedViewUuid);
if (!view) NcError.notFound('Not found');
if (
view.type !== ViewTypes.GRID &&
view.type !== ViewTypes.KANBAN &&
view.type !== ViewTypes.GALLERY
) {
NcError.notFound('Not found');
}
if (view.password && view.password !== param.password) {
return NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
const model = await Model.getByIdOrName({
id: view?.fk_model_id,
});
return await getGroupedDataList({
model,
view,
query: param.query,
groupColumnId: param.groupColumnId,
});
}
async function getGroupedDataList(param: {
model: Model;
view: View;
query: any;
groupColumnId: string;
}) {
const { model, view, query = {}, groupColumnId } = param;
const base = await Base.get(param.model.base_id);
const baseModel = await Model.getBaseModelSQL({
id: model.id,
viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base),
});
const requestObj = await getAst({ model, query: param.query, view });
const listArgs: any = { ...query };
try {
listArgs.filterArr = JSON.parse(listArgs.filterArrJson);
} catch (e) {}
try {
listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
} catch (e) {}
try {
listArgs.options = JSON.parse(listArgs.optionsArrJson);
} catch (e) {}
let data = [];
try {
const groupedData = await baseModel.groupedList({
...listArgs,
groupColumnId,
});
data = await nocoExecute(
{ key: 1, value: requestObj },
groupedData,
{},
listArgs
);
const countArr = await baseModel.groupedListCount({
...listArgs,
groupColumnId,
});
data = data.map((item) => {
// todo: use map to avoid loop
const count =
countArr.find((countItem: any) => countItem.key === item.key)?.count ??
0;
item.value = new PagedResponseImpl(item.value, {
...query,
count: count,
});
return item;
});
} catch (e) {
console.log(e);
NcError.internalServerError('Internal Server Error');
}
return data;
}
export async function dataInsert(param: {
sharedViewUuid: string;
password?: string;
body: any;
files: any[];
siteUrl: string;
}) {
const view = await View.getByUUID(param.sharedViewUuid);
if (!view) NcError.notFound();
if (view.type !== ViewTypes.FORM) NcError.notFound();
if (view.password && view.password !== param.password) {
return NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
const model = await Model.getByIdOrName({
id: view?.fk_model_id,
});
const base = await Base.get(model.base_id);
const project = await base.getProject();
const baseModel = await Model.getBaseModelSQL({
id: model.id,
viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base),
});
await view.getViewWithInfo();
await view.getColumns();
await view.getModelWithInfo();
await view.model.getColumns();
const fields = (view.model.columns = view.columns
.filter((c) => c.show)
.reduce((o, c) => {
o[view.model.columnsById[c.fk_column_id].title] = new Column({
...c,
...view.model.columnsById[c.fk_column_id],
} as any);
return o;
}, {}) as any);
let body = param?.body;
if (typeof body === 'string') body = JSON.parse(body);
const insertObject = Object.entries(body).reduce((obj, [key, val]) => {
if (key in fields) {
obj[key] = val;
}
return obj;
}, {});
const attachments = {};
const storageAdapter = await NcPluginMgrv2.storageAdapter();
for (const file of param.files || []) {
// remove `_` prefix and `[]` suffix
const fieldName = file?.fieldname?.replace(/^_|\[\d*]$/g, '');
const filePath = sanitizeUrlPath([
'v1',
project.title,
model.title,
fieldName,
]);
if (fieldName in fields && fields[fieldName].uidt === UITypes.Attachment) {
attachments[fieldName] = attachments[fieldName] || [];
const fileName = `${nanoid(6)}_${file.originalname}`;
let url = await storageAdapter.fileCreate(
slash(path.join('nc', 'uploads', ...filePath, fileName)),
file
);
if (!url) {
url = `${param.siteUrl}/download/${filePath.join('/')}/${fileName}`;
}
attachments[fieldName].push({
url,
title: file.originalname,
mimetype: file.mimetype,
size: file.size,
icon: mimeIcons[path.extname(file.originalname).slice(1)] || undefined,
});
}
}
for (const [column, data] of Object.entries(attachments)) {
insertObject[column] = JSON.stringify(data);
}
return await baseModel.nestedInsert(insertObject, null);
}
export async function relDataList(param: {
query: any;
sharedViewUuid: string;
password?: string;
columnId: string;
}) {
const view = await View.getByUUID(param.sharedViewUuid);
if (!view) NcError.notFound('Not found');
if (view.type !== ViewTypes.FORM) NcError.notFound('Not found');
if (view.password && view.password !== param.password) {
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
const column = await Column.get({ colId: param.columnId });
const colOptions = await column.getColOptions<LinkToAnotherRecordColumn>();
const model = await colOptions.getRelatedTable();
const base = await Base.get(model.base_id);
const baseModel = await Model.getBaseModelSQL({
id: model.id,
viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base),
});
const requestObj = await getAst({
query: param.query,
model,
extractOnlyPrimaries: true,
});
let data = [];
let count = 0;
try {
data = data = await nocoExecute(
requestObj,
await baseModel.list(param.query),
{},
param.query
);
count = await baseModel.count(param.query);
} catch (e) {
// show empty result instead of throwing error here
// e.g. search some text in a numeric field
}
return new PagedResponseImpl(data, { ...param.query, count });
}
export async function publicMmList(param: {
query: any;
sharedViewUuid: string;
password?: string;
columnId: string;
rowId: string;
}) {
const view = await View.getByUUID(param.sharedViewUuid);
if (!view) NcError.notFound('Not found');
if (view.type !== ViewTypes.GRID) NcError.notFound('Not found');
if (view.password && view.password !== param.password) {
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
const column = await getColumnByIdOrName(
param.columnId,
await view.getModel()
);
if (column.fk_model_id !== view.fk_model_id)
NcError.badRequest("Column doesn't belongs to the model");
const base = await Base.get(view.base_id);
const baseModel = await Model.getBaseModelSQL({
id: view.fk_model_id,
viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base),
});
const key = `List`;
const requestObj: any = {
[key]: 1,
};
const data = (
await nocoExecute(
requestObj,
{
[key]: async (args) => {
return await baseModel.mmList(
{
colId: param.columnId,
parentId: param.rowId,
},
args
);
},
},
{},
{ nested: { [key]: param.query } }
)
)?.[key];
const count: any = await baseModel.mmListCount({
colId: param.columnId,
parentId: param.rowId,
});
return new PagedResponseImpl(data, { ...param.query, count });
}
export async function publicHmList(param: {
query: any;
rowId: string;
sharedViewUuid: string;
password?: string;
columnId: string;
}) {
const view = await View.getByUUID(param.sharedViewUuid);
if (!view) NcError.notFound('Not found');
if (view.type !== ViewTypes.GRID) NcError.notFound('Not found');
if (view.password && view.password !== param.password) {
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
const column = await getColumnByIdOrName(
param.columnId,
await view.getModel()
);
if (column.fk_model_id !== view.fk_model_id)
NcError.badRequest("Column doesn't belongs to the model");
const base = await Base.get(view.base_id);
const baseModel = await Model.getBaseModelSQL({
id: view.fk_model_id,
viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base),
});
const key = `List`;
const requestObj: any = {
[key]: 1,
};
const data = (
await nocoExecute(
requestObj,
{
[key]: async (args) => {
return await baseModel.hmList(
{
colId: param.columnId,
id: param.rowId,
},
args
);
},
},
{},
{ nested: { [key]: param.query } }
)
)?.[key];
const count = await baseModel.hmListCount({
colId: param.columnId,
id: param.rowId,
});
return new PagedResponseImpl(data, { ...param.query, count });
}

100
packages/nocodb/src/lib/services/public/publicMetaservice.ts

@ -0,0 +1,100 @@
import {
ErrorMessages,
LinkToAnotherRecordType,
RelationTypes,
UITypes,
} from 'nocodb-sdk';
import { NcError } from '../../meta/helpers/catchError';
import {
Base,
Column,
LinkToAnotherRecordColumn,
Model,
Project,
View,
} from '../../models';
export async function viewMetaGet(param: {
sharedViewUuid: string;
password: string;
}) {
const view: View & {
relatedMetas?: { [ket: string]: Model };
client?: string;
} = await View.getByUUID(param.sharedViewUuid);
if (!view) NcError.notFound('Not found');
if (view.password && view.password !== param.password) {
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
await view.getFilters();
await view.getSorts();
await view.getViewWithInfo();
await view.getColumns();
await view.getModelWithInfo();
await view.model.getColumns();
const base = await Base.get(view.model.base_id);
view.client = base.type;
// todo: return only required props
delete view['password'];
view.model.columns = view.columns
.filter((c) => {
const column = view.model.columnsById[c.fk_column_id];
return (
c.show ||
(column.rqd && !column.cdf && !column.ai) ||
column.pk ||
view.model.columns.some(
(c1) =>
c1.uidt === UITypes.LinkToAnotherRecord &&
(<LinkToAnotherRecordColumn>c1.colOptions).type ===
RelationTypes.BELONGS_TO &&
view.columns.some((vc) => vc.fk_column_id === c1.id && vc.show) &&
(<LinkToAnotherRecordColumn>c1.colOptions).fk_child_column_id ===
c.fk_column_id
)
);
})
.map(
(c) =>
new Column({ ...c, ...view.model.columnsById[c.fk_column_id] } as any)
) as any;
const relatedMetas = {};
// load related table metas
for (const col of view.model.columns) {
if (UITypes.LinkToAnotherRecord === col.uidt) {
const colOpt = await col.getColOptions<LinkToAnotherRecordType>();
relatedMetas[colOpt.fk_related_model_id] = await Model.getWithInfo({
id: colOpt.fk_related_model_id,
});
if (colOpt.type === 'mm') {
relatedMetas[colOpt.fk_mm_model_id] = await Model.getWithInfo({
id: colOpt.fk_mm_model_id,
});
}
}
}
view.relatedMetas = relatedMetas;
return view;
}
export async function publicSharedBaseGet(param: {
sharedBaseUuid: string;
}): Promise<any> {
const project = await Project.getByUuid(param.sharedBaseUuid);
if (!project) {
NcError.notFound();
}
return { project_id: project.id };
}
Loading…
Cancel
Save