diff --git a/packages/nc-gui/lang/da.json b/packages/nc-gui/lang/da.json
index e67b4258f8..ff663368a2 100644
--- a/packages/nc-gui/lang/da.json
+++ b/packages/nc-gui/lang/da.json
@@ -1,31 +1,31 @@
{
"general": {
- "home": "Hjem",
- "load": "belastning",
- "open": "Åben",
- "close": "Tæt",
+ "home": "Forside",
+ "load": "Indlæs",
+ "open": "Åbn",
+ "close": "Luk",
"yes": "Ja",
- "no": "Ingen",
+ "no": "Nej",
"ok": "Okay",
"and": "Og",
"or": "Eller",
- "add": "Tilføje",
- "edit": "Redigere",
- "remove": "Fjerne",
- "save": "Gemme",
- "cancel": "Afbestille",
+ "add": "Tilføj",
+ "edit": "Redigér",
+ "remove": "Fjern",
+ "save": "Gem",
+ "cancel": "Fortryd",
"submit": "Indsend",
- "create": "skab",
+ "create": "Opret",
"duplicate": "Duplikat",
- "insert": "Indsættes",
- "delete": "Delete.",
- "update": "UPDATE.",
+ "insert": "Indsæt",
+ "delete": "Slet",
+ "update": "Opdatér",
"rename": "Omdøb",
- "reload": "Genindlæsning",
+ "reload": "Genindlæs",
"reset": "Nulstil",
- "install": "Installere",
- "show": "At vise",
- "hide": "Skjule",
+ "install": "Installer",
+ "show": "Vis",
+ "hide": "Skjul",
"showAll": "Vis alt",
"hideAll": "Gem alt",
"showMore": "Vis mere",
@@ -75,7 +75,7 @@
"hideField": "Skjul felt",
"sortAsc": "Sortere stigende",
"sortDesc": "Sortere nedadgående",
- "geoDataField": "GeoData Field"
+ "geoDataField": "GeoData-felt"
},
"objects": {
"project": "Projekt",
@@ -92,15 +92,15 @@
"records": "Optegnelser.",
"webhook": "WebHook.",
"webhooks": "Webhooks.",
- "view": "Udsigt",
- "views": "Visninger.",
+ "view": "Visning",
+ "views": "Visninger",
"viewType": {
- "grid": "Grid.",
+ "grid": "Grid",
"gallery": "Galleri",
"form": "Formular",
"kanban": "Kanban.",
"calendar": "Kalender",
- "map": "Map"
+ "map": "Kort"
},
"user": "Bruger",
"users": "Brugere",
@@ -209,7 +209,7 @@
"advancedSettings": "Avancerede indstillinger",
"codeSnippet": "Kodeuddrag",
"keyboardShortcut": "Tastaturgenveje",
- "generateRandomName": "Generate Random Name"
+ "generateRandomName": "Generér Tilfældigt Navn"
},
"labels": {
"createdBy": "Oprettet af",
@@ -217,7 +217,7 @@
"projName": "Projekt navn",
"tableName": "Tabelnavn.",
"viewName": "Se navn",
- "viewLink": "Se Link.",
+ "viewLink": "Vis Link",
"columnName": "Kolonne navn",
"columnType": "Kolonne type",
"roleName": "Rolle navn",
@@ -235,7 +235,7 @@
"action": "Handling",
"actions": "Handlinger",
"operation": "Operation",
- "operationSub": "Sub Operation",
+ "operationSub": "Underordnet operation",
"operationType": "Driftstype",
"operationSubType": "Drift Undertype",
"description": "Beskrivelse",
@@ -257,7 +257,7 @@
"barcodeFormat": "Stregkodeformat",
"qrCodeValueTooLong": "For mange tegn til en QR-kode",
"barcodeValueTooLong": "For mange tegn til en stregkode",
- "yourLocation": "Your Location",
+ "yourLocation": "Din Placering",
"lng": "Lng",
"lat": "Lat",
"aggregateFunction": "Aggregate Function.",
@@ -283,8 +283,8 @@
},
"docReference": "Dokumentreference.",
"selectUserRole": "Vælg brugerrolle",
- "childTable": "Børnebord",
- "childColumn": "Barn kolonne",
+ "childTable": "Undertabel",
+ "childColumn": "Underkolonner",
"linkToAnotherRecord": "Link til en anden post",
"onUpdate": "På opdatering",
"onDelete": "På Delete.",
@@ -379,36 +379,36 @@
"reloadRoles": "Genindlæs roller",
"nextPage": "Næste side",
"prevPage": "Forrige side",
- "nextRecord": "Næste rekord.",
- "previousRecord": "Tidligere rekord.",
+ "nextRecord": "Næste post",
+ "previousRecord": "Forrige post",
"copyApiURL": "COPY API URL.",
- "createTable": "Tabel Create.",
- "refreshTable": "Tabeller opdatere",
- "renameTable": "Bord omdøb",
- "deleteTable": "TABEL DELETE.",
+ "createTable": "Opret tabel",
+ "refreshTable": "Genopfrisk Tabeller",
+ "renameTable": "Omdøb Tabel",
+ "deleteTable": "Slet Tabel",
"addField": "Tilføj nyt felt til denne tabel",
- "setDisplay": "Set as Display value",
- "addRow": "Tilføj ny række",
- "saveRow": "Gem ro",
+ "setDisplay": "Sæt som visningsværdi",
+ "addRow": "Tilføj ny post",
+ "saveRow": "Gem række",
"saveAndExit": "Gem og afslutning",
"saveAndStay": "Gem og bliv",
"insertRow": "Indsæt ny række",
- "deleteRow": "DELETE ROW.",
- "duplicateRow": "Duplicate Row",
+ "deleteRow": "Slet Række",
+ "duplicateRow": "Dupliker Række",
"deleteSelectedRow": "Slet de valgte rækker",
"importExcel": "Import Excel.",
"importCSV": "Import CSV.",
"downloadCSV": "Download som CSV.",
"downloadExcel": "Download som XLSX",
"uploadCSV": "Upload CSV.",
- "import": "Importere",
+ "import": "Import",
"importMetadata": "Import metadata.",
"exportMetadata": "Eksport metadata.",
"clearMetadata": "Klare metadata.",
"exportToFile": "Eksporter til filer",
"changePwd": "Skift kodeord",
"createView": "Opret en visning",
- "shareView": "Del View",
+ "shareView": "Del Visning",
"listSharedView": "Shared View List.",
"ListView": "Visninger List",
"copyView": "Kopi visning",
@@ -429,23 +429,23 @@
"importZip": "Import Zip.",
"metaSync": "Synkroniser nu",
"settings": "Indstillinger.",
- "previewAs": "Forhåndsvisning så",
- "resetReview": "Nulstil preview.",
- "testDbConn": "Test Database Connection.",
+ "previewAs": "Forhåndsvisning som",
+ "resetReview": "Nulstil forhåndsvisning",
+ "testDbConn": "Test Database Forbindelse",
"removeDbFromEnv": "Fjern databasen fra miljøet",
"editConnJson": "Rediger forbindelse JSON",
- "sponsorUs": "Sponsor os",
+ "sponsorUs": "Sponsorer os",
"sendEmail": "SEND E-MAIL",
"addUserToProject": "Tilføj bruger til projekt",
"getApiSnippet": "Hent API-snippet",
- "clearCell": "Klar celle",
+ "clearCell": "Ryd celle",
"addFilterGroup": "Tilføj filtergruppe",
"linkRecord": "Link record",
"addNewRecord": "Tilføj ny post",
"useConnectionUrl": "Brug forbindelses-URL",
"toggleCommentsDraw": "Toggle kommentarer tegne",
"expandRecord": "Udvid optegnelse",
- "deleteRecord": "Slet registrering",
+ "deleteRecord": "Slet Post",
"erd": {
"showColumns": "Vis kolonner",
"showPkAndFk": "Vis primære og fremmede nøgler",
@@ -461,8 +461,8 @@
"addOrEditStack": "Tilføj / Rediger stak"
},
"map": {
- "mappedBy": "Mapped By",
- "chooseMappingField": "Choose a Mapping Field"
+ "mappedBy": "Kortlagt af",
+ "chooseMappingField": "Vælg et kortlægningsfelt"
}
},
"tooltip": {
@@ -530,9 +530,9 @@
"orgViewer": "Seeren har ikke lov til at oprette nye projekter, men kan få adgang til alle inviterede projekter."
},
"map": {
- "overLimit": "You're over the limit.",
- "closeLimit": "You're getting close to the limit.",
- "limitNumber": "The limit of markers shown in a Map View is 1000 records."
+ "overLimit": "Du er over grænsen.",
+ "closeLimit": "Du nærmer dig grænsen.",
+ "limitNumber": "Grænsen for antallet af markeringer, der vises i en kortvisning, er 1000 poster."
},
"footerInfo": "Rækker per side",
"upload": "Vælg fil for at uploade",
@@ -616,7 +616,7 @@
"gallery": "Tilføj Gallery View.",
"form": "Tilføj formularvisning",
"kanban": "Tilføj Kanban View.",
- "map": "Add Map View",
+ "map": "Tilføj Kortvisning",
"calendar": "Tilføj kalendervisning"
},
"tablesMetadataInSync": "Tabeller Metadata er synkroniseret",
@@ -648,11 +648,11 @@
"deleteViewConfirmation": "Er du sikker på, at du vil slette denne visning?",
"deleteTableConfirmation": "Ønsker du at slette tabellen",
"showM2mTables": "Vis M2M-tabeller",
- "showM2mTablesDesc": "Many-to-many relation is supported via a junction table & is hidden by default. Enable this option to list all such tables along with existing tables.",
- "showNullInCells": "Show NULL in Cells",
+ "showM2mTablesDesc": "Mange-til-mange-relationer understøttes via en sammenknytnings-tabel (junction table) og er skjult som standard. Aktiver denne indstilling for at få vist alle sådanne tabeller sammen med eksisterende tabeller.",
+ "showNullInCells": "Vis NULL i celler",
"showNullInCellsDesc": "Display 'NULL' tag in cells holding NULL value. This helps differentiate against cells holding EMPTY string.",
- "showNullAndEmptyInFilter": "Show NULL and EMPTY in Filter",
- "showNullAndEmptyInFilterDesc": "Enable 'additional' filters to differentiate fields containing NULL & Empty Strings. Default support for Blank treats both NULL & Empty strings alike.",
+ "showNullAndEmptyInFilter": "Vis NULL og EMPTY i Filter",
+ "showNullAndEmptyInFilterDesc": "Aktiver \"yderligere\" filtre for at skelne mellem felter, der indeholder NULL og tomme strenge. Standardunderstøttelse for Blank behandler både NULL- og tomme strenge ens.",
"deleteKanbanStackConfirmation": "Hvis du sletter denne stak, fjernes også valgmuligheden `{stackToBeDeleted}` fra `{groupingField}`. Posterne vil blive flyttet til stakken \"uncategorized\".",
"computedFieldEditWarning": "Beregnet felt: indholdet er skrivebeskyttet. Brug kolonne-redigeringsmenuen til at omkonfigurere",
"computedFieldDeleteWarning": "Beregnet felt: indholdet er skrivebeskyttet. Det er ikke muligt at slette indholdet.",
@@ -707,7 +707,7 @@
"nameShouldStartWithAnAlphabetOr_": "Navnet skal starte med et alfabet eller _",
"followingCharactersAreNotAllowed": "Følgende tegn er ikke tilladt",
"columnNameRequired": "Kolonnens navn er påkrævet",
- "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
+ "columnNameExceedsCharacters": "Længden af kolonnenavnet overstiger maks. {value} tegn",
"projectNameExceeds50Characters": "Projektnavnet overstiger 50 tegn",
"projectNameCannotStartWithSpace": "Projektnavnet kan ikke begynde med et mellemrum",
"requiredField": "Obligatorisk felt",
@@ -740,7 +740,7 @@
},
"success": {
"columnDuplicated": "Kolonne duplikeret med succes",
- "rowDuplicatedWithoutSavedYet": "Row duplicated (not saved)",
+ "rowDuplicatedWithoutSavedYet": "Række duplikeret (ikke gemt)",
"updatedUIACL": "Opdateret UI ACL for tabeller med succes",
"pluginUninstalled": "Plugin afinstalleret med succes",
"pluginSettingsSaved": "Plugin-indstillingerne er gemt med succes",
diff --git a/packages/noco-docs/content/en/setup-and-usages/table-operations.md b/packages/noco-docs/content/en/setup-and-usages/table-operations.md
index 2aeeae8c43..f2b4b8ac61 100644
--- a/packages/noco-docs/content/en/setup-and-usages/table-operations.md
+++ b/packages/noco-docs/content/en/setup-and-usages/table-operations.md
@@ -70,7 +70,8 @@ After the click, it will show a menu and you can enter the column name and choos
You can also click `Show more` for additional menu options.
-
+![Screenshot 2023-03-03 at 8 13 07 PM](https://user-images.githubusercontent.com/86527202/222749857-0e793db2-a5d2-4b54-8d23-2a0cbbec8f5d.png)
+
Click `Save` button to create the new column.
diff --git a/packages/nocodb-sdk/src/lib/Api.ts b/packages/nocodb-sdk/src/lib/Api.ts
index 865e4c994f..b421ae426d 100644
--- a/packages/nocodb-sdk/src/lib/Api.ts
+++ b/packages/nocodb-sdk/src/lib/Api.ts
@@ -2159,6 +2159,7 @@ export interface ViewColumnReqType {
* Model for Visibility Rule Request
*/
export type VisibilityRuleReqType = {
+ id?: string | null;
disabled?: {
/** Model for Bool */
commenter?: BoolType;
diff --git a/packages/nocodb-sdk/src/lib/sqlUi/OracleUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/OracleUi.ts
index 9dafe83191..3498a0835e 100644
--- a/packages/nocodb-sdk/src/lib/sqlUi/OracleUi.ts
+++ b/packages/nocodb-sdk/src/lib/sqlUi/OracleUi.ts
@@ -1,3 +1,4 @@
+import { NormalColumnRequestType } from '../Api'
import UITypes from '../UITypes';
import { IDType } from './index';
@@ -794,7 +795,10 @@ export class OracleUi {
}
}
- static getDataTypeForUiType(col: { uidt?: UITypes }, idType?: IDType) {
+ static getDataTypeForUiType(
+ col: { uidt: UITypes | NormalColumnRequestType['uidt'] },
+ idType?: IDType
+ ) {
const colProp: any = {};
switch (col.uidt) {
case 'ID':
diff --git a/packages/nocodb-sdk/src/lib/sqlUi/SqlUiFactory.ts b/packages/nocodb-sdk/src/lib/sqlUi/SqlUiFactory.ts
index 6e79066cad..3eef12622a 100644
--- a/packages/nocodb-sdk/src/lib/sqlUi/SqlUiFactory.ts
+++ b/packages/nocodb-sdk/src/lib/sqlUi/SqlUiFactory.ts
@@ -1,3 +1,4 @@
+import { BoolType } from '../Api'
import UITypes from '../UITypes';
import { MssqlUi } from './MssqlUi';
@@ -56,12 +57,12 @@ export type SqlUIColumn = {
dt?: string;
dtx?: string;
ct?: string;
- nrqd?: boolean;
- rqd?: boolean;
+ nrqd?: BoolType;
+ rqd?: BoolType;
ck?: string;
- pk?: boolean;
- un?: boolean;
- ai?: boolean;
+ pk?: BoolType;
+ un?: BoolType;
+ ai?: BoolType;
cdf?: string | any;
clen?: number | any;
np?: string;
diff --git a/packages/nocodb/package-lock.json b/packages/nocodb/package-lock.json
index 94179fd694..8783f36d99 100644
--- a/packages/nocodb/package-lock.json
+++ b/packages/nocodb/package-lock.json
@@ -66,7 +66,7 @@
"multer": "^1.4.2",
"mysql2": "^2.2.5",
"nanoid": "^3.1.20",
- "nc-help": "0.2.85",
+ "nc-help": "0.2.87",
"nc-lib-gui": "0.105.3",
"nc-plugin": "0.1.2",
"ncp": "^2.0.0",
@@ -11311,9 +11311,9 @@
}
},
"node_modules/nc-help": {
- "version": "0.2.85",
- "resolved": "https://registry.npmjs.org/nc-help/-/nc-help-0.2.85.tgz",
- "integrity": "sha512-EOyrc2PuRUJzv73jHNHmUR6YhC0TlJG0DTY/sug7BF4MJAVPJgyavJnrqkRC7g0NS4xociki9gs5MbLRjlRwtQ==",
+ "version": "0.2.87",
+ "resolved": "https://registry.npmjs.org/nc-help/-/nc-help-0.2.87.tgz",
+ "integrity": "sha512-Zlg06ialvylBEE1qtvjlNKxZrPShzXwvy3WG7nfw+8GngOkQBCTlKguejT2Kq4Gfb5378WPX1APXtsetMKBrRA==",
"dependencies": {
"@rudderstack/rudder-sdk-node": "^1.1.3",
"axios": "^0.21.1",
@@ -28029,9 +28029,9 @@
"integrity": "sha512-3AryS9uwa5NfISLxMciUonrH7YfXp+nlahB9T7girXIsLQrmwX4MdnuKs32akduCOGpKmjTJSWmATULbuMkbfw=="
},
"nc-help": {
- "version": "0.2.85",
- "resolved": "https://registry.npmjs.org/nc-help/-/nc-help-0.2.85.tgz",
- "integrity": "sha512-EOyrc2PuRUJzv73jHNHmUR6YhC0TlJG0DTY/sug7BF4MJAVPJgyavJnrqkRC7g0NS4xociki9gs5MbLRjlRwtQ==",
+ "version": "0.2.87",
+ "resolved": "https://registry.npmjs.org/nc-help/-/nc-help-0.2.87.tgz",
+ "integrity": "sha512-Zlg06ialvylBEE1qtvjlNKxZrPShzXwvy3WG7nfw+8GngOkQBCTlKguejT2Kq4Gfb5378WPX1APXtsetMKBrRA==",
"requires": {
"@rudderstack/rudder-sdk-node": "^1.1.3",
"axios": "^0.21.1",
diff --git a/packages/nocodb/package.json b/packages/nocodb/package.json
index 9dee15b3f9..617f60d5b4 100644
--- a/packages/nocodb/package.json
+++ b/packages/nocodb/package.json
@@ -107,7 +107,7 @@
"multer": "^1.4.2",
"mysql2": "^2.2.5",
"nanoid": "^3.1.20",
- "nc-help": "0.2.85",
+ "nc-help": "0.2.87",
"nc-lib-gui": "0.105.3",
"nc-plugin": "0.1.2",
"ncp": "^2.0.0",
diff --git a/packages/nocodb/src/lib/Noco.ts b/packages/nocodb/src/lib/Noco.ts
index 9099de28b4..151f211c65 100644
--- a/packages/nocodb/src/lib/Noco.ts
+++ b/packages/nocodb/src/lib/Noco.ts
@@ -21,7 +21,7 @@ import { NC_LICENSE_KEY } from './constants';
import Migrator from './db/sql-migrator/lib/KnexMigrator';
import Store from './models/Store';
import NcConfigFactory from './utils/NcConfigFactory';
-import { Tele } from 'nc-help';
+import { T } from 'nc-help';
import NcProjectBuilderCE from './v1-legacy/NcProjectBuilder';
import NcProjectBuilderEE from './v1-legacy/NcProjectBuilderEE';
@@ -45,7 +45,7 @@ import User from './models/User';
import * as http from 'http';
import weAreHiring from './utils/weAreHiring';
import getInstance from './utils/getInstance';
-import initAdminFromEnv from './meta/api/userApi/initAdminFromEnv';
+import initAdminFromEnv from './services/userService/initAdminFromEnv';
const log = debug('nc:app');
require('dotenv').config();
@@ -275,10 +275,10 @@ export default class Noco {
}
next();
});
- Tele.init({
+ T.init({
instance: getInstance,
});
- Tele.emit('evt_app_started', await User.count());
+ T.emit('evt_app_started', await User.count());
console.log(`App started successfully.\nVisit -> ${Noco.dashboardUrl}`);
weAreHiring();
return this.router;
@@ -531,7 +531,7 @@ export default class Noco {
if (!serverId) {
await Noco._ncMeta.metaInsert('', '', 'nc_store', {
key: 'nc_server_id',
- value: (serverId = Tele.id),
+ value: (serverId = T.id),
});
}
process.env.NC_SERVER_UUID = serverId;
diff --git a/packages/nocodb/src/lib/controllers/apiTokenController.ts b/packages/nocodb/src/lib/controllers/apiTokenController.ts
new file mode 100644
index 0000000000..d472c9a800
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/apiTokenController.ts
@@ -0,0 +1,49 @@
+import { Request, Response, Router } from 'express';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { apiTokenService } from '../services';
+
+export async function apiTokenList(req: Request, res: Response) {
+ res.json(await apiTokenService.apiTokenList({ userId: req['user'].id }));
+}
+
+export async function apiTokenCreate(req: Request, res: Response) {
+ res.json(
+ await apiTokenService.apiTokenCreate({
+ tokenBody: req.body,
+ userId: req['user'].id,
+ })
+ );
+}
+
+export async function apiTokenDelete(req: Request, res: Response) {
+ res.json(
+ await apiTokenService.apiTokenDelete({
+ token: req.params.token,
+ user: req['user'],
+ })
+ );
+}
+
+// todo: add reset token api to regenerate token
+
+// deprecated apis
+const router = Router({ mergeParams: true });
+
+router.get(
+ '/api/v1/db/meta/projects/:projectId/api-tokens',
+ metaApiMetrics,
+ ncMetaAclMw(apiTokenList, 'apiTokenList')
+);
+router.post(
+ '/api/v1/db/meta/projects/:projectId/api-tokens',
+ metaApiMetrics,
+ ncMetaAclMw(apiTokenCreate, 'apiTokenCreate')
+);
+router.delete(
+ '/api/v1/db/meta/projects/:projectId/api-tokens/:token',
+ metaApiMetrics,
+ ncMetaAclMw(apiTokenDelete, 'apiTokenDelete')
+);
+
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/attachmentController.ts b/packages/nocodb/src/lib/controllers/attachmentController.ts
new file mode 100644
index 0000000000..8b9bd0eff7
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/attachmentController.ts
@@ -0,0 +1,127 @@
+import { Request, Response, Router } from 'express';
+import multer from 'multer';
+import { OrgUserRoles, ProjectRoles } from 'nocodb-sdk';
+import path from 'path';
+import Noco from '../Noco';
+import { MetaTable } from '../utils/globals';
+import extractProjectIdAndAuthenticate from '../meta/helpers/extractProjectIdAndAuthenticate';
+import catchError, { NcError } from '../meta/helpers/catchError';
+import { NC_ATTACHMENT_FIELD_SIZE } from '../constants';
+import { getCacheMiddleware } from '../meta/api/helpers';
+import { attachmentService } from '../services';
+
+const isUploadAllowedMw = async (req: Request, _res: Response, next: any) => {
+ if (!req['user']?.id) {
+ if (!req['user']?.isPublicBase) {
+ NcError.unauthorized('Unauthorized');
+ }
+ }
+
+ try {
+ // check user is super admin or creator
+ if (
+ req['user'].roles?.includes(OrgUserRoles.SUPER_ADMIN) ||
+ req['user'].roles?.includes(OrgUserRoles.CREATOR) ||
+ req['user'].roles?.includes(ProjectRoles.EDITOR) ||
+ // if viewer then check at-least one project have editor or higher role
+ // todo: cache
+ !!(await Noco.ncMeta
+ .knex(MetaTable.PROJECT_USERS)
+ .where(function () {
+ this.where('roles', ProjectRoles.OWNER);
+ this.orWhere('roles', ProjectRoles.CREATOR);
+ this.orWhere('roles', ProjectRoles.EDITOR);
+ })
+ .andWhere('fk_user_id', req['user'].id)
+ .first())
+ )
+ return next();
+ } catch {}
+ NcError.badRequest('Upload not allowed');
+};
+
+export async function upload(req: Request, res: Response) {
+ const attachments = await attachmentService.upload({
+ files: (req as any).files,
+ path: req.query?.path as string,
+ });
+
+ res.json(attachments);
+}
+
+export async function uploadViaURL(req: Request, res: Response) {
+ const attachments = await attachmentService.uploadViaURL({
+ urls: req.body,
+ path: req.query?.path as string,
+ });
+
+ res.json(attachments);
+}
+
+export async function fileRead(req, res) {
+ try {
+ const { img, type } = await attachmentService.fileRead({
+ path: path.join('nc', 'uploads', req.params?.[0]),
+ });
+
+ res.writeHead(200, { 'Content-Type': type });
+ res.end(img, 'binary');
+ } catch (e) {
+ console.log(e);
+ res.status(404).send('Not found');
+ }
+}
+
+const router = Router({ mergeParams: true });
+
+router.get(
+ /^\/dl\/([^/]+)\/([^/]+)\/(.+)$/,
+ getCacheMiddleware(),
+ async (req, res) => {
+ try {
+ const { img, type } = await attachmentService.fileRead({
+ path: path.join(
+ 'nc',
+ req.params[0],
+ req.params[1],
+ 'uploads',
+ ...req.params[2].split('/')
+ ),
+ });
+
+ res.writeHead(200, { 'Content-Type': type });
+ res.end(img, 'binary');
+ } catch (e) {
+ res.status(404).send('Not found');
+ }
+ }
+);
+
+router.post(
+ '/api/v1/db/storage/upload',
+ multer({
+ storage: multer.diskStorage({}),
+ limits: {
+ fieldSize: NC_ATTACHMENT_FIELD_SIZE,
+ },
+ }).any(),
+ [
+ extractProjectIdAndAuthenticate,
+ catchError(isUploadAllowedMw),
+ catchError(upload),
+ ]
+);
+
+router.post(
+ '/api/v1/db/storage/upload-by-url',
+
+ [
+ extractProjectIdAndAuthenticate,
+ catchError(isUploadAllowedMw),
+ catchError(uploadViaURL),
+ ]
+);
+
+router.get(/^\/download\/(.+)$/, getCacheMiddleware(), catchError(fileRead));
+
+export default router;
diff --git a/packages/nocodb/src/lib/meta/api/auditApis.ts b/packages/nocodb/src/lib/controllers/auditController.ts
similarity index 50%
rename from packages/nocodb/src/lib/meta/api/auditApis.ts
rename to packages/nocodb/src/lib/controllers/auditController.ts
index fdf9f70daf..73ab7dafa4 100644
--- a/packages/nocodb/src/lib/meta/api/auditApis.ts
+++ b/packages/nocodb/src/lib/controllers/auditController.ts
@@ -1,39 +1,24 @@
import { Request, Response, Router } from 'express';
-import Audit from '../../models/Audit';
-import { AuditOperationSubTypes, AuditOperationTypes } from 'nocodb-sdk';
-import Model from '../../models/Model';
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-
-import DOMPurify from 'isomorphic-dompurify';
-import { getAjvValidatorMw } from './helpers';
+import Audit from '../models/Audit';
+import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { auditService } from '../services';
export async function commentRow(req: Request, res) {
res.json(
- await Audit.insert({
- ...req.body,
- user: (req as any).user?.email,
- op_type: AuditOperationTypes.COMMENT,
+ await auditService.commentRow({
+ rowId: req.params.rowId,
+ user: (req as any).user,
+ body: req.body,
})
);
}
export async function auditRowUpdate(req: Request, res) {
- const model = await Model.getByIdOrName({ id: req.body.fk_model_id });
res.json(
- await Audit.insert({
- fk_model_id: req.body.fk_model_id,
- row_id: req.params.rowId,
- op_type: AuditOperationTypes.DATA,
- op_sub_type: AuditOperationSubTypes.UPDATE,
- description: DOMPurify.sanitize(
- `Table ${model.table_name} : field ${req.body.column_name} got changed from ${req.body.prev_value} to ${req.body.value}`
- ),
- details: DOMPurify.sanitize(`${req.body.column_name}
- : ${req.body.prev_value}
- ${req.body.value}`),
- ip: (req as any).clientIp,
- user: (req as any).user?.email,
+ await auditService.auditRowUpdate({
+ rowId: req.params.rowId,
+ body: req.body,
})
);
}
@@ -70,12 +55,10 @@ router.get(
);
router.post(
'/api/v1/db/meta/audits/comments',
- getAjvValidatorMw('swagger.json#/components/schemas/CommentReq'),
ncMetaAclMw(commentRow, 'commentRow')
);
router.post(
'/api/v1/db/meta/audits/rows/:rowId/update',
- getAjvValidatorMw('swagger.json#/components/schemas/AuditRowUpdateReq'),
ncMetaAclMw(auditRowUpdate, 'auditRowUpdate')
);
router.get(
diff --git a/packages/nocodb/src/lib/controllers/baseController.ts b/packages/nocodb/src/lib/controllers/baseController.ts
new file mode 100644
index 0000000000..f35deb5069
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/baseController.ts
@@ -0,0 +1,91 @@
+import { Request, Response } from 'express';
+import { BaseListType } from 'nocodb-sdk';
+import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
+import Base from '../models/Base';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+
+import { baseService } from '../services';
+
+async function baseGet(req: Request, res: Response) {
+ const base = await baseService.baseGetWithConfig({
+ baseId: req.params.baseId,
+ });
+
+ res.json(base);
+}
+
+async function baseUpdate(req: Request, res: Response) {
+ const base = await baseService.baseUpdate({
+ baseId: req.params.baseId,
+ base: req.body,
+ projectId: req.params.projectId,
+ });
+ res.json(base);
+}
+
+async function baseList(
+ req: Request,
+ res: Response
+) {
+ const bases = await baseService.baseList({
+ projectId: req.params.projectId,
+ });
+
+ res // todo: pagination
+ .json({
+ bases: new PagedResponseImpl(bases, {
+ count: bases.length,
+ limit: bases.length,
+ }),
+ });
+}
+
+export async function baseDelete(
+ req: Request,
+ res: Response
+) {
+ const result = await baseService.baseDelete({
+ baseId: req.params.baseId,
+ });
+ res.json(result);
+}
+
+async function baseCreate(req: Request, res) {
+ const base = await baseService.baseCreate({
+ projectId: req.params.projectId,
+ base: req.body,
+ });
+
+ res.json(base);
+}
+
+const initRoutes = (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')
+ );
+};
+
+export default initRoutes;
diff --git a/packages/nocodb/src/lib/meta/api/cacheApis.ts b/packages/nocodb/src/lib/controllers/cacheController.ts
similarity index 70%
rename from packages/nocodb/src/lib/meta/api/cacheApis.ts
rename to packages/nocodb/src/lib/controllers/cacheController.ts
index 8c5af7fa0d..751c26178c 100644
--- a/packages/nocodb/src/lib/meta/api/cacheApis.ts
+++ b/packages/nocodb/src/lib/controllers/cacheController.ts
@@ -1,9 +1,9 @@
-import catchError from '../helpers/catchError';
-import NocoCache from '../../cache/NocoCache';
+import catchError from '../meta/helpers/catchError';
import { Router } from 'express';
+import { cacheService } from '../services';
export async function cacheGet(_, res) {
- const data = await NocoCache.export();
+ const data = await cacheService.cacheGet();
res.set({
'Content-Type': 'application/json',
'Content-Disposition': `attachment; filename="cache-export.json"`,
@@ -12,7 +12,7 @@ export async function cacheGet(_, res) {
}
export async function cacheDelete(_, res) {
- return res.json(await NocoCache.destroy());
+ return res.json(await cacheService.cacheDelete());
}
const router = Router();
diff --git a/packages/nocodb/src/lib/controllers/columnController.ts b/packages/nocodb/src/lib/controllers/columnController.ts
new file mode 100644
index 0000000000..a6a230cfe1
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/columnController.ts
@@ -0,0 +1,77 @@
+import { Request, Response, Router } from 'express';
+import { ColumnReqType, TableType, UITypes } from 'nocodb-sdk';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { columnService } from '../services';
+
+export async function columnGet(req: Request, res: Response) {
+ res.json(await columnService.columnGet({ columnId: req.params.columnId }));
+}
+
+export async function columnAdd(
+ req: Request,
+ res: Response
+) {
+ res.json(
+ await columnService.columnAdd({
+ tableId: req.params.tableId,
+ column: req.body,
+ req,
+ })
+ );
+}
+
+export async function columnSetAsPrimary(req: Request, res: Response) {
+ res.json(
+ await columnService.columnSetAsPrimary({ columnId: req.params.columnId })
+ );
+}
+
+export async function columnUpdate(req: Request, res: Response) {
+ res.json(
+ await columnService.columnUpdate({
+ columnId: req.params.columnId,
+ column: req.body,
+ req,
+ })
+ );
+}
+
+export async function columnDelete(req: Request, res: Response) {
+ res.json(
+ await columnService.columnDelete({ columnId: req.params.columnId, req })
+ );
+}
+
+const router = Router({ mergeParams: true });
+
+router.post(
+ '/api/v1/db/meta/tables/:tableId/columns/',
+ metaApiMetrics,
+ ncMetaAclMw(columnAdd, 'columnAdd')
+);
+
+router.patch(
+ '/api/v1/db/meta/columns/:columnId',
+ metaApiMetrics,
+ ncMetaAclMw(columnUpdate, 'columnUpdate')
+);
+
+router.delete(
+ '/api/v1/db/meta/columns/:columnId',
+ metaApiMetrics,
+ ncMetaAclMw(columnDelete, 'columnDelete')
+);
+
+router.get(
+ '/api/v1/db/meta/columns/:columnId',
+ metaApiMetrics,
+ ncMetaAclMw(columnGet, 'columnGet')
+);
+
+router.post(
+ '/api/v1/db/meta/columns/:columnId/primary',
+ metaApiMetrics,
+ ncMetaAclMw(columnSetAsPrimary, 'columnSetAsPrimary')
+);
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/dataControllers/bulkDataAliasController.ts b/packages/nocodb/src/lib/controllers/dataControllers/bulkDataAliasController.ts
new file mode 100644
index 0000000000..7aa016ef2e
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/dataControllers/bulkDataAliasController.ts
@@ -0,0 +1,92 @@
+import { Request, Response, Router } from 'express';
+import { bulkDataService } from '../../services';
+import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw';
+import apiMetrics from '../../meta/helpers/apiMetrics';
+
+async function bulkDataInsert(req: Request, res: Response) {
+ res.json(
+ await bulkDataService.bulkDataInsert({
+ body: req.body,
+ cookie: req,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ })
+ );
+}
+
+async function bulkDataUpdate(req: Request, res: Response) {
+ res.json(
+ await bulkDataService.bulkDataUpdate({
+ body: req.body,
+ cookie: req,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ })
+ );
+}
+
+// todo: Integrate with filterArrJson bulkDataUpdateAll
+async function bulkDataUpdateAll(req: Request, res: Response) {
+ res.json(
+ await bulkDataService.bulkDataUpdateAll({
+ body: req.body,
+ cookie: req,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ query: req.query,
+ })
+ );
+}
+
+async function bulkDataDelete(req: Request, res: Response) {
+ res.json(
+ await bulkDataService.bulkDataDelete({
+ body: req.body,
+ cookie: req,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ })
+ );
+}
+
+// todo: Integrate with filterArrJson bulkDataDeleteAll
+async function bulkDataDeleteAll(req: Request, res: Response) {
+ res.json(
+ await bulkDataService.bulkDataDeleteAll({
+ // cookie: req,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ query: req.query,
+ })
+ );
+}
+
+const router = Router({ mergeParams: true });
+
+router.post(
+ '/api/v1/db/data/bulk/:orgs/:projectName/:tableName',
+ apiMetrics,
+ ncMetaAclMw(bulkDataInsert, 'bulkDataInsert')
+);
+router.patch(
+ '/api/v1/db/data/bulk/:orgs/:projectName/:tableName',
+ apiMetrics,
+ ncMetaAclMw(bulkDataUpdate, 'bulkDataUpdate')
+);
+router.patch(
+ '/api/v1/db/data/bulk/:orgs/:projectName/:tableName/all',
+ apiMetrics,
+ ncMetaAclMw(bulkDataUpdateAll, 'bulkDataUpdateAll')
+);
+router.delete(
+ '/api/v1/db/data/bulk/:orgs/:projectName/:tableName',
+ apiMetrics,
+ ncMetaAclMw(bulkDataDelete, 'bulkDataDelete')
+);
+router.delete(
+ '/api/v1/db/data/bulk/:orgs/:projectName/:tableName/all',
+ apiMetrics,
+ ncMetaAclMw(bulkDataDeleteAll, 'bulkDataDeleteAll')
+);
+
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/dataControllers/dataAliasController.ts b/packages/nocodb/src/lib/controllers/dataControllers/dataAliasController.ts
new file mode 100644
index 0000000000..b5eda23c74
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/dataControllers/dataAliasController.ts
@@ -0,0 +1,259 @@
+import { Request, Response, Router } from 'express';
+import { dataService } from '../../services';
+import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw';
+import apiMetrics from '../../meta/helpers/apiMetrics';
+import { parseHrtimeToSeconds } from '../../meta/api/helpers';
+
+// todo: Handle the error case where view doesnt belong to model
+async function dataList(req: Request, res: Response) {
+ const startTime = process.hrtime();
+ const responseData = await dataService.dataList({
+ query: req.query,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ viewName: req.params.viewName,
+ });
+ const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime));
+ res.setHeader('xc-db-response', elapsedSeconds);
+ res.json(responseData);
+}
+
+async function dataFindOne(req: Request, res: Response) {
+ res.json(
+ await dataService.dataFindOne({
+ query: req.query,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ viewName: req.params.viewName,
+ })
+ );
+}
+
+async function dataGroupBy(req: Request, res: Response) {
+ res.json(
+ await dataService.dataGroupBy({
+ query: req.query,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ viewName: req.params.viewName,
+ })
+ );
+}
+
+async function dataCount(req: Request, res: Response) {
+ const countResult = await dataService.dataCount({
+ query: req.query,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ viewName: req.params.viewName,
+ });
+
+ res.json(countResult);
+}
+
+async function dataInsert(req: Request, res: Response) {
+ res.json(
+ await dataService.dataInsert({
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ viewName: req.params.viewName,
+ body: req.body,
+ cookie: req,
+ })
+ );
+}
+
+async function dataUpdate(req: Request, res: Response) {
+ res.json(
+ await dataService.dataUpdate({
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ viewName: req.params.viewName,
+ body: req.body,
+ cookie: req,
+ rowId: req.params.rowId,
+ })
+ );
+}
+
+async function dataDelete(req: Request, res: Response) {
+ res.json(
+ await dataService.dataDelete({
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ viewName: req.params.viewName,
+ cookie: req,
+ rowId: req.params.rowId,
+ })
+ );
+}
+
+async function dataRead(req: Request, res: Response) {
+ res.json(
+ await dataService.dataRead({
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ viewName: req.params.viewName,
+ rowId: req.params.rowId,
+ query: req.query,
+ })
+ );
+}
+
+async function dataExist(req: Request, res: Response) {
+ res.json(
+ await dataService.dataExist({
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ viewName: req.params.viewName,
+ rowId: req.params.rowId,
+ query: req.query,
+ })
+ );
+}
+
+// todo: Handle the error case where view doesnt belong to model
+async function groupedDataList(req: Request, res: Response) {
+ const startTime = process.hrtime();
+ const groupedData = await dataService.groupedDataList({
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ viewName: req.params.viewName,
+ query: req.query,
+ columnId: req.params.columnId,
+ });
+ const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime));
+ res.setHeader('xc-db-response', elapsedSeconds);
+ res.json(groupedData);
+}
+const router = Router({ mergeParams: true });
+
+// table data crud apis
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName',
+ apiMetrics,
+ ncMetaAclMw(dataList, 'dataList')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/find-one',
+ apiMetrics,
+ ncMetaAclMw(dataFindOne, 'dataFindOne')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/groupby',
+ apiMetrics,
+ ncMetaAclMw(dataGroupBy, 'dataGroupBy')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/group/:columnId',
+ apiMetrics,
+ ncMetaAclMw(groupedDataList, 'groupedDataList')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/exist',
+ apiMetrics,
+ ncMetaAclMw(dataExist, 'dataExist')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/count',
+ apiMetrics,
+ ncMetaAclMw(dataCount, 'dataCount')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/count',
+ apiMetrics,
+ ncMetaAclMw(dataCount, 'dataCount')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId',
+ apiMetrics,
+ ncMetaAclMw(dataRead, 'dataRead')
+);
+
+router.patch(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId',
+ apiMetrics,
+ ncMetaAclMw(dataUpdate, 'dataUpdate')
+);
+
+router.delete(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId',
+ apiMetrics,
+ ncMetaAclMw(dataDelete, 'dataDelete')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName',
+ apiMetrics,
+ ncMetaAclMw(dataList, 'dataList')
+);
+
+// table view data crud apis
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName',
+ apiMetrics,
+ ncMetaAclMw(dataList, 'dataList')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/find-one',
+ apiMetrics,
+ ncMetaAclMw(dataFindOne, 'dataFindOne')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/groupby',
+ apiMetrics,
+ ncMetaAclMw(dataGroupBy, 'dataGroupBy')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/group/:columnId',
+ apiMetrics,
+ ncMetaAclMw(groupedDataList, 'groupedDataList')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId/exist',
+ apiMetrics,
+ ncMetaAclMw(dataExist, 'dataExist')
+);
+
+router.post(
+ '/api/v1/db/data/:orgs/:projectName/:tableName',
+ apiMetrics,
+ ncMetaAclMw(dataInsert, 'dataInsert')
+);
+
+router.post(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName',
+ apiMetrics,
+ ncMetaAclMw(dataInsert, 'dataInsert')
+);
+
+router.patch(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId',
+ apiMetrics,
+ ncMetaAclMw(dataUpdate, 'dataUpdate')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId',
+ apiMetrics,
+ ncMetaAclMw(dataRead, 'dataRead')
+);
+
+router.delete(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId',
+ apiMetrics,
+ ncMetaAclMw(dataDelete, 'dataDelete')
+);
+
+export default router;
diff --git a/packages/nocodb/src/lib/meta/api/dataApis/dataAliasExportApis.ts b/packages/nocodb/src/lib/controllers/dataControllers/dataAliasExportController.ts
similarity index 88%
rename from packages/nocodb/src/lib/meta/api/dataApis/dataAliasExportApis.ts
rename to packages/nocodb/src/lib/controllers/dataControllers/dataAliasExportController.ts
index 8c6cf38bea..0b85b87801 100644
--- a/packages/nocodb/src/lib/meta/api/dataApis/dataAliasExportApis.ts
+++ b/packages/nocodb/src/lib/controllers/dataControllers/dataAliasExportController.ts
@@ -1,13 +1,13 @@
import { Request, Response, Router } from 'express';
import * as XLSX from 'xlsx';
-import ncMetaAclMw from '../../helpers/ncMetaAclMw';
+import apiMetrics from '../../meta/helpers/apiMetrics';
+import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw';
+import { View } from '../../models';
import {
extractCsvData,
extractXlsxData,
- getViewAndModelFromRequestByAliasOrId,
-} from './helpers';
-import apiMetrics from '../../helpers/apiMetrics';
-import View from '../../../models/View';
+} from '../../services/dataService/helpers';
+import { getViewAndModelFromRequestByAliasOrId } from './helpers';
async function excelDataExport(req: Request, res: Response) {
const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
diff --git a/packages/nocodb/src/lib/controllers/dataControllers/dataAliasNestedController.ts b/packages/nocodb/src/lib/controllers/dataControllers/dataAliasNestedController.ts
new file mode 100644
index 0000000000..9fc5169ee3
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/dataControllers/dataAliasNestedController.ts
@@ -0,0 +1,137 @@
+import { Request, Response, Router } from 'express';
+import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw';
+import apiMetrics from '../../meta/helpers/apiMetrics';
+import { dataAliasNestedService } from '../../services';
+
+// todo: handle case where the given column is not ltar
+export async function mmList(req: Request, res: Response) {
+ res.json(
+ await dataAliasNestedService.mmList({
+ query: req.query,
+ columnName: req.params.columnName,
+ rowId: req.params.rowId,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ })
+ );
+}
+
+export async function mmExcludedList(req: Request, res: Response) {
+ res.json(
+ await dataAliasNestedService.mmExcludedList({
+ query: req.query,
+ columnName: req.params.columnName,
+ rowId: req.params.rowId,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ })
+ );
+}
+
+export async function hmExcludedList(req: Request, res: Response) {
+ res.json(
+ await dataAliasNestedService.hmExcludedList({
+ query: req.query,
+ columnName: req.params.columnName,
+ rowId: req.params.rowId,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ })
+ );
+}
+
+export async function btExcludedList(req: Request, res: Response) {
+ res.json(
+ await dataAliasNestedService.btExcludedList({
+ query: req.query,
+ columnName: req.params.columnName,
+ rowId: req.params.rowId,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ })
+ );
+}
+
+// todo: handle case where the given column is not ltar
+export async function hmList(req: Request, res: Response) {
+ res.json(
+ await dataAliasNestedService.hmList({
+ query: req.query,
+ columnName: req.params.columnName,
+ rowId: req.params.rowId,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ })
+ );
+}
+
+//@ts-ignore
+async function relationDataRemove(req, res) {
+ await dataAliasNestedService.relationDataRemove({
+ columnName: req.params.columnName,
+ rowId: req.params.rowId,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ cookie: req,
+ refRowId: req.params.refRowId,
+ });
+
+ res.json({ msg: 'success' });
+}
+
+//@ts-ignore
+// todo: Give proper error message when reference row is already related and handle duplicate ref row id in hm
+async function relationDataAdd(req, res) {
+ await dataAliasNestedService.relationDataAdd({
+ columnName: req.params.columnName,
+ rowId: req.params.rowId,
+ projectName: req.params.projectName,
+ tableName: req.params.tableName,
+ cookie: req,
+ refRowId: req.params.refRowId,
+ });
+
+ res.json({ msg: 'success' });
+}
+
+const router = Router({ mergeParams: true });
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/mm/:columnName/exclude',
+ apiMetrics,
+ ncMetaAclMw(mmExcludedList, 'mmExcludedList')
+);
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/hm/:columnName/exclude',
+ apiMetrics,
+ ncMetaAclMw(hmExcludedList, 'hmExcludedList')
+);
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/bt/:columnName/exclude',
+ apiMetrics,
+ ncMetaAclMw(btExcludedList, 'btExcludedList')
+);
+
+router.post(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/:relationType/:columnName/:refRowId',
+ apiMetrics,
+ ncMetaAclMw(relationDataAdd, 'relationDataAdd')
+);
+router.delete(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/:relationType/:columnName/:refRowId',
+ apiMetrics,
+ ncMetaAclMw(relationDataRemove, 'relationDataRemove')
+);
+
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/mm/:columnName',
+ apiMetrics,
+ ncMetaAclMw(mmList, 'mmList')
+);
+router.get(
+ '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/hm/:columnName',
+ apiMetrics,
+ ncMetaAclMw(hmList, 'hmList')
+);
+
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/dataControllers/dataController.ts b/packages/nocodb/src/lib/controllers/dataControllers/dataController.ts
new file mode 100644
index 0000000000..fb5c35ab17
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/dataControllers/dataController.ts
@@ -0,0 +1,192 @@
+import { Request, Response, Router } from 'express';
+import { dataService } from '../../services';
+import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw';
+import apiMetrics from '../../meta/helpers/apiMetrics';
+
+export async function dataList(req: Request, res: Response) {
+ res.json(
+ await dataService.dataListByViewId({
+ viewId: req.params.viewId,
+ query: req.query,
+ })
+ );
+}
+
+export async function mmList(req: Request, res: Response) {
+ res.json(
+ await dataService.mmList({
+ viewId: req.params.viewId,
+ colId: req.params.colId,
+ rowId: req.params.rowId,
+ query: req.query,
+ })
+ );
+}
+
+export async function mmExcludedList(req: Request, res: Response) {
+ res.json(
+ await dataService.mmExcludedList({
+ viewId: req.params.viewId,
+ colId: req.params.colId,
+ rowId: req.params.rowId,
+ query: req.query,
+ })
+ );
+}
+
+export async function hmExcludedList(req: Request, res: Response) {
+ res.json(
+ await dataService.hmExcludedList({
+ viewId: req.params.viewId,
+ colId: req.params.colId,
+ rowId: req.params.rowId,
+ query: req.query,
+ })
+ );
+}
+
+export async function btExcludedList(req: Request, res: Response) {
+ res.json(
+ await dataService.btExcludedList({
+ viewId: req.params.viewId,
+ colId: req.params.colId,
+ rowId: req.params.rowId,
+ query: req.query,
+ })
+ );
+}
+
+export async function hmList(req: Request, res: Response) {
+ res.json(
+ await dataService.hmList({
+ viewId: req.params.viewId,
+ colId: req.params.colId,
+ rowId: req.params.rowId,
+ query: req.query,
+ })
+ );
+}
+
+async function dataRead(req: Request, res: Response) {
+ res.json(
+ await dataService.dataReadByViewId({
+ viewId: req.params.viewId,
+ rowId: req.params.rowId,
+ query: req.query,
+ })
+ );
+}
+
+async function dataInsert(req: Request, res: Response) {
+ res.json(
+ await dataService.dataInsertByViewId({
+ viewId: req.params.viewId,
+ body: req.body,
+ cookie: req,
+ })
+ );
+}
+
+async function dataUpdate(req: Request, res: Response) {
+ res.json(
+ await dataService.dataUpdateByViewId({
+ viewId: req.params.viewId,
+ rowId: req.params.rowId,
+ body: req.body,
+ cookie: req,
+ })
+ );
+}
+
+async function dataDelete(req: Request, res: Response) {
+ res.json(
+ await dataService.dataDeleteByViewId({
+ viewId: req.params.viewId,
+ rowId: req.params.rowId,
+ cookie: req,
+ })
+ );
+}
+
+async function relationDataDelete(req, res) {
+ await dataService.relationDataDelete({
+ viewId: req.params.viewId,
+ colId: req.params.colId,
+ childId: req.params.childId,
+ rowId: req.params.rowId,
+ cookie: req,
+ });
+
+ res.json({ msg: 'success' });
+}
+
+//@ts-ignore
+async function relationDataAdd(req, res) {
+ await dataService.relationDataAdd({
+ viewId: req.params.viewId,
+ colId: req.params.colId,
+ childId: req.params.childId,
+ rowId: req.params.rowId,
+ cookie: req,
+ });
+
+ res.json({ msg: 'success' });
+}
+
+const router = Router({ mergeParams: true });
+
+router.get('/data/:viewId/', apiMetrics, ncMetaAclMw(dataList, 'dataList'));
+router.post(
+ '/data/:viewId/',
+ apiMetrics,
+ ncMetaAclMw(dataInsert, 'dataInsert')
+);
+router.get(
+ '/data/:viewId/:rowId',
+ apiMetrics,
+ ncMetaAclMw(dataRead, 'dataRead')
+);
+router.patch(
+ '/data/:viewId/:rowId',
+ apiMetrics,
+ ncMetaAclMw(dataUpdate, 'dataUpdate')
+);
+router.delete(
+ '/data/:viewId/:rowId',
+ apiMetrics,
+ ncMetaAclMw(dataDelete, 'dataDelete')
+);
+
+router.get(
+ '/data/:viewId/:rowId/mm/:colId',
+ apiMetrics,
+ ncMetaAclMw(mmList, 'mmList')
+);
+router.get(
+ '/data/:viewId/:rowId/hm/:colId',
+ apiMetrics,
+ ncMetaAclMw(hmList, 'hmList')
+);
+
+router.get(
+ '/data/:viewId/:rowId/mm/:colId/exclude',
+ ncMetaAclMw(mmExcludedList, 'mmExcludedList')
+);
+router.get(
+ '/data/:viewId/:rowId/hm/:colId/exclude',
+ ncMetaAclMw(hmExcludedList, 'hmExcludedList')
+);
+router.get(
+ '/data/:viewId/:rowId/bt/:colId/exclude',
+ ncMetaAclMw(btExcludedList, 'btExcludedList')
+);
+
+router.post(
+ '/data/:viewId/:rowId/:relationType/:colId/:childId',
+ ncMetaAclMw(relationDataAdd, 'relationDataAdd')
+);
+router.delete(
+ '/data/:viewId/:rowId/:relationType/:colId/:childId',
+ ncMetaAclMw(relationDataDelete, 'relationDataDelete')
+);
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/dataControllers/helpers.ts b/packages/nocodb/src/lib/controllers/dataControllers/helpers.ts
new file mode 100644
index 0000000000..2ca5cd17e1
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/dataControllers/helpers.ts
@@ -0,0 +1,270 @@
+import { NcError } from '../../meta/helpers/catchError';
+import Project from '../../models/Project';
+import Model from '../../models/Model';
+import View from '../../models/View';
+import { Request } from 'express';
+import Base from '../../models/Base';
+import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
+import { isSystemColumn, UITypes } from 'nocodb-sdk';
+
+import * as XLSX from 'xlsx';
+import Column from '../../models/Column';
+import LookupColumn from '../../models/LookupColumn';
+import LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn';
+
+import papaparse from 'papaparse';
+import { dataService } from '../../services';
+export async function getViewAndModelFromRequestByAliasOrId(
+ req:
+ | Request<{ projectName: string; tableName: string; viewName?: string }>
+ | Request
+) {
+ const project = await Project.getWithInfoByTitleOrId(req.params.projectName);
+
+ const model = await Model.getByAliasOrId({
+ project_id: project.id,
+ aliasOrId: req.params.tableName,
+ });
+ const view =
+ req.params.viewName &&
+ (await View.getByTitleOrId({
+ titleOrId: req.params.viewName,
+ fk_model_id: model.id,
+ }));
+ if (!model) NcError.notFound('Table not found');
+ return { model, view };
+}
+
+export async function extractXlsxData(param: {
+ view: View;
+ query: any;
+ siteUrl: string;
+}) {
+ const { view, query, siteUrl } = param;
+ const base = await Base.get(view.base_id);
+
+ await view.getModelWithInfo();
+ await view.getColumns();
+
+ 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);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: view.model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ const { offset, dbRows, elapsed } = await dataService.getDbRows({
+ baseModel,
+ view,
+ query,
+ siteUrl,
+ });
+
+ const fields = query.fields as string[];
+
+ const data = XLSX.utils.json_to_sheet(dbRows, { header: fields });
+
+ return { offset, dbRows, elapsed, data };
+}
+
+export async function extractCsvData(view: View, req: Request) {
+ const base = await Base.get(view.base_id);
+ const fields = req.query.fields;
+
+ await view.getModelWithInfo();
+ await view.getColumns();
+
+ 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);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: view.model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ const { offset, dbRows, elapsed } = await dataService.getDbRows({
+ baseModel,
+ view,
+ query: req.query,
+ siteUrl: (req as any).ncSiteUrl,
+ });
+
+ const data = papaparse.unparse(
+ {
+ fields: view.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,
+ }
+ );
+
+ return { offset, dbRows, elapsed, data };
+}
+//
+// async function getDbRows(baseModel, view: View, req: Request) {
+// 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;
+//
+// const listArgs: any = { ...req.query };
+// try {
+// listArgs.filterArr = JSON.parse(listArgs.filterArrJson);
+// } catch (e) {}
+// try {
+// listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
+// } catch (e) {}
+//
+// for (
+// elapsed = 0;
+// elapsed < timeout;
+// offset += limit,
+// temp = process.hrtime(startTime),
+// elapsed = temp[0] * 1000 + temp[1] / 1000000
+// ) {
+// const rows = await nocoExecute(
+// await getAst({
+// query: req.query,
+// includePkByDefault: false,
+// model: view.model,
+// view,
+// }),
+// await baseModel.list({ ...listArgs, offset, limit }),
+// {},
+// req.query
+// );
+//
+// if (!rows?.length) {
+// offset = -1;
+// break;
+// }
+//
+// for (const row of rows) {
+// const dbRow = { ...row };
+//
+// for (const column of view.model.columns) {
+// if (isSystemColumn(column) && !view.show_system_fields) continue;
+// dbRow[column.title] = await serializeCellValue({
+// value: row[column.title],
+// column,
+// siteUrl: req['ncSiteUrl'],
+// });
+// }
+// dbRows.push(dbRow);
+// }
+// }
+// return { offset, dbRows, elapsed };
+// }
+
+export async function serializeCellValue({
+ value,
+ column,
+ siteUrl,
+}: {
+ column?: Column;
+ value: any;
+ siteUrl: string;
+}) {
+ 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.path ? `${siteUrl}/${attachment.path}` : attachment.url
+ )})`
+ );
+ }
+ case UITypes.Lookup:
+ {
+ const colOptions = await column.getColOptions();
+ const lookupColumn = await colOptions.getLookupColumn();
+ return (
+ await Promise.all(
+ [...(Array.isArray(value) ? value : [value])].map(async (v) =>
+ serializeCellValue({
+ value: v,
+ column: lookupColumn,
+ siteUrl,
+ })
+ )
+ )
+ ).join(', ');
+ }
+ break;
+ case UITypes.LinkToAnotherRecord:
+ {
+ const colOptions =
+ await column.getColOptions();
+ 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;
+ }
+}
+
+export async function getColumnByIdOrName(
+ columnNameOrId: string,
+ model: Model
+) {
+ const column = (await model.getColumns()).find(
+ (c) =>
+ c.title === columnNameOrId ||
+ c.id === columnNameOrId ||
+ c.column_name === columnNameOrId
+ );
+
+ if (!column)
+ NcError.notFound(`Column with id/name '${columnNameOrId}' is not found`);
+
+ return column;
+}
diff --git a/packages/nocodb/src/lib/controllers/dataControllers/index.ts b/packages/nocodb/src/lib/controllers/dataControllers/index.ts
new file mode 100644
index 0000000000..122ec7fac2
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/dataControllers/index.ts
@@ -0,0 +1,15 @@
+import dataController from './dataController';
+import oldDataController from './oldDataController';
+import dataAliasController from './dataAliasController';
+import bulkDataAliasController from './bulkDataAliasController';
+import dataAliasNestedController from './dataAliasNestedController';
+import dataAliasExportController from './dataAliasExportController';
+
+export {
+ dataController,
+ oldDataController,
+ dataAliasController,
+ bulkDataAliasController,
+ dataAliasNestedController,
+ dataAliasExportController,
+};
diff --git a/packages/nocodb/src/lib/meta/api/dataApis/oldDataApis.ts b/packages/nocodb/src/lib/controllers/dataControllers/oldDataController.ts
similarity index 89%
rename from packages/nocodb/src/lib/meta/api/dataApis/oldDataApis.ts
rename to packages/nocodb/src/lib/controllers/dataControllers/oldDataController.ts
index 5df008eb8a..3492ff7361 100644
--- a/packages/nocodb/src/lib/meta/api/dataApis/oldDataApis.ts
+++ b/packages/nocodb/src/lib/controllers/dataControllers/oldDataController.ts
@@ -1,14 +1,14 @@
import { Request, Response, Router } from 'express';
-import Model from '../../../models/Model';
+import Model from '../../models/Model';
import { nocoExecute } from 'nc-help';
-import Base from '../../../models/Base';
-import NcConnectionMgrv2 from '../../../utils/common/NcConnectionMgrv2';
-import View from '../../../models/View';
-import ncMetaAclMw from '../../helpers/ncMetaAclMw';
-import Project from '../../../models/Project';
-import { NcError } from '../../helpers/catchError';
-import apiMetrics from '../../helpers/apiMetrics';
-import getAst from '../../../db/sql-data-mapper/lib/sql/helpers/getAst';
+import Base from '../../models/Base';
+import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
+import View from '../../models/View';
+import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw';
+import Project from '../../models/Project';
+import { NcError } from '../../meta/helpers/catchError';
+import apiMetrics from '../../meta/helpers/apiMetrics';
+import getAst from '../../db/sql-data-mapper/lib/sql/helpers/getAst';
export async function dataList(req: Request, res: Response) {
const { model, view } = await getViewAndModelFromRequest(req);
diff --git a/packages/nocodb/src/lib/meta/api/exportApis.ts b/packages/nocodb/src/lib/controllers/exportController.ts
similarity index 81%
rename from packages/nocodb/src/lib/meta/api/exportApis.ts
rename to packages/nocodb/src/lib/controllers/exportController.ts
index 7c0a5e013e..38ae156cb9 100644
--- a/packages/nocodb/src/lib/meta/api/exportApis.ts
+++ b/packages/nocodb/src/lib/controllers/exportController.ts
@@ -1,7 +1,7 @@
import { Request, Response, Router } from 'express';
-import View from '../../models/View';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { extractCsvData } from './dataApis/helpers';
+import View from '../models/View';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { extractCsvData } from './dataControllers/helpers';
async function exportCsv(req: Request, res: Response) {
const view = await View.get(req.params.viewId);
diff --git a/packages/nocodb/src/lib/controllers/filterController.ts b/packages/nocodb/src/lib/controllers/filterController.ts
new file mode 100644
index 0000000000..bf48e30765
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/filterController.ts
@@ -0,0 +1,115 @@
+import { Request, Response, Router } from 'express';
+import { FilterReqType } from 'nocodb-sdk';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+
+import { filterService } from '../services';
+
+// @ts-ignore
+export async function filterGet(req: Request, res: Response) {
+ res.json(await filterService.filterGet({ filterId: req.params.filterId }));
+}
+
+// @ts-ignore
+export async function filterList(req: Request, res: Response) {
+ res.json(
+ await filterService.filterList({
+ viewId: req.params.viewId,
+ })
+ );
+}
+
+// @ts-ignore
+export async function filterChildrenRead(req: Request, res: Response) {
+ const filter = await filterService.filterChildrenList({
+ filterId: req.params.filterParentId,
+ });
+
+ res.json(filter);
+}
+
+export async function filterCreate(req: Request, res) {
+ const filter = await filterService.filterCreate({
+ filter: req.body,
+ viewId: req.params.viewId,
+ });
+ res.json(filter);
+}
+
+export async function filterUpdate(req, res) {
+ const filter = await filterService.filterUpdate({
+ filterId: req.params.filterId,
+ filter: req.body,
+ });
+ res.json(filter);
+}
+
+export async function filterDelete(req: Request, res: Response) {
+ const filter = await filterService.filterDelete({
+ filterId: req.params.filterId,
+ });
+ res.json(filter);
+}
+
+export async function hookFilterList(req: Request, res: Response) {
+ res.json(
+ await filterService.hookFilterList({
+ hookId: req.params.hookId,
+ })
+ );
+}
+
+export async function hookFilterCreate(
+ req: Request,
+ res
+) {
+ const filter = await filterService.hookFilterCreate({
+ filter: req.body,
+ hookId: req.params.hookId,
+ });
+ res.json(filter);
+}
+
+const router = Router({ mergeParams: true });
+router.get(
+ '/api/v1/db/meta/views/:viewId/filters',
+ metaApiMetrics,
+ ncMetaAclMw(filterList, 'filterList')
+);
+router.post(
+ '/api/v1/db/meta/views/:viewId/filters',
+ metaApiMetrics,
+ ncMetaAclMw(filterCreate, 'filterCreate')
+);
+
+router.get(
+ '/api/v1/db/meta/hooks/:hookId/filters',
+ ncMetaAclMw(hookFilterList, 'filterList')
+);
+router.post(
+ '/api/v1/db/meta/hooks/:hookId/filters',
+ metaApiMetrics,
+ ncMetaAclMw(hookFilterCreate, 'filterCreate')
+);
+
+router.get(
+ '/api/v1/db/meta/filters/:filterId',
+ metaApiMetrics,
+ ncMetaAclMw(filterGet, 'filterGet')
+);
+router.patch(
+ '/api/v1/db/meta/filters/:filterId',
+ metaApiMetrics,
+ ncMetaAclMw(filterUpdate, 'filterUpdate')
+);
+router.delete(
+ '/api/v1/db/meta/filters/:filterId',
+ metaApiMetrics,
+ ncMetaAclMw(filterDelete, 'filterDelete')
+);
+router.get(
+ '/api/v1/db/meta/filters/:filterParentId/children',
+ metaApiMetrics,
+ ncMetaAclMw(filterChildrenRead, 'filterChildrenRead')
+);
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/formViewColumnController.ts b/packages/nocodb/src/lib/controllers/formViewColumnController.ts
new file mode 100644
index 0000000000..8b13309411
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/formViewColumnController.ts
@@ -0,0 +1,21 @@
+import { Request, Response, Router } from 'express';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { formViewColumnService } from '../services';
+
+export async function columnUpdate(req: Request, res: Response) {
+ res.json(
+ await formViewColumnService.columnUpdate({
+ formViewColumnId: req.params.formViewColumnId,
+ formViewColumn: req.body,
+ })
+ );
+}
+
+const router = Router({ mergeParams: true });
+router.patch(
+ '/api/v1/db/meta/form-columns/:formViewColumnId',
+ metaApiMetrics,
+ ncMetaAclMw(columnUpdate, 'columnUpdate')
+);
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/formViewController.ts b/packages/nocodb/src/lib/controllers/formViewController.ts
new file mode 100644
index 0000000000..2bc61fd58d
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/formViewController.ts
@@ -0,0 +1,48 @@
+import { Request, Response, Router } from 'express';
+import { FormType } from 'nocodb-sdk';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { formViewService } from '../services';
+
+export async function formViewGet(req: Request, res: Response) {
+ const formViewData = await formViewService.formViewGet({
+ formViewId: req.params.formViewId,
+ });
+ res.json(formViewData);
+}
+
+export async function formViewCreate(req: Request, res) {
+ const view = await formViewService.formViewCreate({
+ body: req.body,
+ tableId: req.params.tableId,
+ });
+ res.json(view);
+}
+
+export async function formViewUpdate(req, res) {
+ res.json(
+ await formViewService.formViewUpdate({
+ formViewId: req.params.formViewId,
+ body: req.body,
+ })
+ );
+}
+
+const router = Router({ mergeParams: true });
+router.post(
+ '/api/v1/db/meta/tables/:tableId/forms',
+ metaApiMetrics,
+ ncMetaAclMw(formViewCreate, 'formViewCreate')
+);
+router.get(
+ '/api/v1/db/meta/forms/:formViewId',
+ metaApiMetrics,
+ ncMetaAclMw(formViewGet, 'formViewGet')
+);
+router.patch(
+ '/api/v1/db/meta/forms/:formViewId',
+ metaApiMetrics,
+ ncMetaAclMw(formViewUpdate, 'formViewUpdate')
+);
+
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/galleryViewController.ts b/packages/nocodb/src/lib/controllers/galleryViewController.ts
new file mode 100644
index 0000000000..f54dc901cd
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/galleryViewController.ts
@@ -0,0 +1,49 @@
+import { Request, Response, Router } from 'express';
+import { GalleryType } from 'nocodb-sdk';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { galleryViewService } from '../services';
+
+export async function galleryViewGet(req: Request, res: Response) {
+ res.json(
+ await galleryViewService.galleryViewGet({
+ galleryViewId: req.params.galleryViewId,
+ })
+ );
+}
+
+export async function galleryViewCreate(req: Request, res) {
+ const view = await galleryViewService.galleryViewCreate({
+ gallery: req.body,
+ // todo: sanitize
+ tableId: req.params.tableId,
+ });
+ res.json(view);
+}
+
+export async function galleryViewUpdate(req, res) {
+ res.json(
+ await galleryViewService.galleryViewUpdate({
+ galleryViewId: req.params.galleryViewId,
+ gallery: req.body,
+ })
+ );
+}
+
+const router = Router({ mergeParams: true });
+router.post(
+ '/api/v1/db/meta/tables/:tableId/galleries',
+ metaApiMetrics,
+ ncMetaAclMw(galleryViewCreate, 'galleryViewCreate')
+);
+router.patch(
+ '/api/v1/db/meta/galleries/:galleryViewId',
+ metaApiMetrics,
+ ncMetaAclMw(galleryViewUpdate, 'galleryViewUpdate')
+);
+router.get(
+ '/api/v1/db/meta/galleries/:galleryViewId',
+ metaApiMetrics,
+ ncMetaAclMw(galleryViewGet, 'galleryViewGet')
+);
+export default router;
diff --git a/packages/nocodb/src/lib/meta/api/gridViewColumnApis.ts b/packages/nocodb/src/lib/controllers/gridViewColumnController.ts
similarity index 50%
rename from packages/nocodb/src/lib/meta/api/gridViewColumnApis.ts
rename to packages/nocodb/src/lib/controllers/gridViewColumnController.ts
index 28669a6a2f..1ce0a41be7 100644
--- a/packages/nocodb/src/lib/meta/api/gridViewColumnApis.ts
+++ b/packages/nocodb/src/lib/controllers/gridViewColumnController.ts
@@ -1,17 +1,23 @@
import { Request, Response, Router } from 'express';
-import GridViewColumn from '../../models/GridViewColumn';
-import { Tele } from 'nc-help';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { gridViewColumnService } from '../services';
export async function columnList(req: Request, res: Response) {
- res.json(await GridViewColumn.list(req.params.gridViewId));
+ res.json(
+ await gridViewColumnService.columnList({
+ gridViewId: req.params.gridViewId,
+ })
+ );
}
export async function gridColumnUpdate(req: Request, res: Response) {
- Tele.emit('evt', { evt_type: 'gridViewColumn:updated' });
- res.json(await GridViewColumn.update(req.params.gridViewColumnId, req.body));
+ res.json(
+ await gridViewColumnService.gridColumnUpdate({
+ gridViewColumnId: req.params.gridViewColumnId,
+ grid: req.body,
+ })
+ );
}
const router = Router({ mergeParams: true });
@@ -23,7 +29,6 @@ router.get(
router.patch(
'/api/v1/db/meta/grid-columns/:gridViewColumnId',
metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/GridColumnReq'),
ncMetaAclMw(gridColumnUpdate, 'gridColumnUpdate')
);
export default router;
diff --git a/packages/nocodb/src/lib/controllers/gridViewController.ts b/packages/nocodb/src/lib/controllers/gridViewController.ts
new file mode 100644
index 0000000000..865ccecdb6
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/gridViewController.ts
@@ -0,0 +1,34 @@
+import { Request, Router } from 'express';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { gridViewService } from '../services';
+
+export async function gridViewCreate(req: Request, res) {
+ const view = await gridViewService.gridViewCreate({
+ grid: req.body,
+ tableId: req.params.tableId,
+ });
+ res.json(view);
+}
+
+export async function gridViewUpdate(req, res) {
+ res.json(
+ await gridViewService.gridViewUpdate({
+ viewId: req.params.viewId,
+ grid: req.body,
+ })
+ );
+}
+
+const router = Router({ mergeParams: true });
+router.post(
+ '/api/v1/db/meta/tables/:tableId/grids/',
+ metaApiMetrics,
+ ncMetaAclMw(gridViewCreate, 'gridViewCreate')
+);
+router.patch(
+ '/api/v1/db/meta/grids/:viewId',
+ metaApiMetrics,
+ ncMetaAclMw(gridViewUpdate, 'gridViewUpdate')
+);
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/hookController.ts b/packages/nocodb/src/lib/controllers/hookController.ts
new file mode 100644
index 0000000000..c75e2b82d1
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/hookController.ts
@@ -0,0 +1,98 @@
+import catchError from '../meta/helpers/catchError';
+import { Request, Response, Router } from 'express';
+import { HookListType, HookType } from 'nocodb-sdk';
+import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { hookService } from '../services';
+
+export async function hookList(
+ req: Request,
+ res: Response
+) {
+ // todo: pagination
+ res.json(
+ new PagedResponseImpl(
+ await hookService.hookList({ tableId: req.params.tableId })
+ )
+ );
+}
+
+export async function hookCreate(
+ req: Request,
+ res: Response
+) {
+ const hook = await hookService.hookCreate({
+ hook: req.body,
+ tableId: req.params.tableId,
+ });
+ res.json(hook);
+}
+
+export async function hookDelete(
+ req: Request,
+ res: Response
+) {
+ res.json(await hookService.hookDelete({ hookId: req.params.hookId }));
+}
+
+export async function hookUpdate(
+ req: Request,
+ res: Response
+) {
+ res.json(
+ await hookService.hookUpdate({ hookId: req.params.hookId, hook: req.body })
+ );
+}
+
+export async function hookTest(req: Request, res: Response) {
+ await hookService.hookTest({
+ hookTest: req.body,
+ tableId: req.params.tableId,
+ });
+ res.json({ msg: 'Success' });
+}
+
+export async function tableSampleData(req: Request, res: Response) {
+ res // todo: pagination
+ .json(
+ await hookService.tableSampleData({
+ tableId: req.params.tableId,
+ // todo: replace any with type
+ operation: req.params.operation as any,
+ })
+ );
+}
+
+const router = Router({ mergeParams: true });
+router.get(
+ '/api/v1/db/meta/tables/:tableId/hooks',
+ metaApiMetrics,
+ ncMetaAclMw(hookList, 'hookList')
+);
+router.post(
+ '/api/v1/db/meta/tables/:tableId/hooks/test',
+ metaApiMetrics,
+ ncMetaAclMw(hookTest, 'hookTest')
+);
+router.post(
+ '/api/v1/db/meta/tables/:tableId/hooks',
+ metaApiMetrics,
+ ncMetaAclMw(hookCreate, 'hookCreate')
+);
+router.delete(
+ '/api/v1/db/meta/hooks/:hookId',
+ metaApiMetrics,
+ ncMetaAclMw(hookDelete, 'hookDelete')
+);
+router.patch(
+ '/api/v1/db/meta/hooks/:hookId',
+ metaApiMetrics,
+ ncMetaAclMw(hookUpdate, 'hookUpdate')
+);
+router.get(
+ '/api/v1/db/meta/tables/:tableId/hooks/samplePayload/:operation',
+ metaApiMetrics,
+ catchError(tableSampleData)
+);
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/hookFilterController.ts b/packages/nocodb/src/lib/controllers/hookFilterController.ts
new file mode 100644
index 0000000000..c83a754d05
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/hookFilterController.ts
@@ -0,0 +1,90 @@
+import { Request, Response, Router } from 'express';
+import { T } from 'nc-help';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { hookFilterService } from '../services';
+
+export async function filterGet(req: Request, res: Response) {
+ const filter = await hookFilterService.filterGet({
+ hookId: req.params.hookId,
+ });
+
+ res.json(filter);
+}
+
+export async function filterList(req: Request, res: Response) {
+ const filter = await hookFilterService.filterList({
+ hookId: req.params.hookId,
+ });
+
+ res.json(filter);
+}
+
+export async function filterChildrenRead(req: Request, res: Response) {
+ const filter = await hookFilterService.filterChildrenRead({
+ hookId: req.params.hookId,
+ filterParentId: req.params.filterParentId,
+ });
+
+ res.json(filter);
+}
+
+export async function filterCreate(req: Request, res) {
+ const filter = await hookFilterService.filterCreate({
+ filter: req.body,
+ hookId: req.params.hookId,
+ });
+
+ res.json(filter);
+}
+
+export async function filterUpdate(req, res) {
+ const filter = await hookFilterService.filterUpdate({
+ filterId: req.params.filterId,
+ filter: req.body,
+ hookId: req.params.hookId,
+ });
+
+ res.json(filter);
+}
+
+export async function filterDelete(req: Request, res: Response) {
+ const filter = await hookFilterService.filterDelete({
+ filterId: req.params.filterId,
+ });
+ T.emit('evt', { evt_type: 'hookFilter:deleted' });
+ res.json(filter);
+}
+
+const router = Router({ mergeParams: true });
+router.get(
+ '/hooks/:hookId/filters/',
+ metaApiMetrics,
+ ncMetaAclMw(filterList, 'filterList')
+);
+router.post(
+ '/hooks/:hookId/filters/',
+ metaApiMetrics,
+ ncMetaAclMw(filterCreate, 'filterCreate')
+);
+router.get(
+ '/hooks/:hookId/filters/:filterId',
+ metaApiMetrics,
+ ncMetaAclMw(filterGet, 'filterGet')
+);
+router.patch(
+ '/hooks/:hookId/filters/:filterId',
+ metaApiMetrics,
+ ncMetaAclMw(filterUpdate, 'filterUpdate')
+);
+router.delete(
+ '/hooks/:hookId/filters/:filterId',
+ metaApiMetrics,
+ ncMetaAclMw(filterDelete, 'filterDelete')
+);
+router.get(
+ '/hooks/:hookId/filters/:filterParentId/children',
+ metaApiMetrics,
+ ncMetaAclMw(filterChildrenRead, 'filterChildrenRead')
+);
+export default router;
diff --git a/packages/nocodb/src/lib/meta/api/kanbanViewApis.ts b/packages/nocodb/src/lib/controllers/kanbanViewController.ts
similarity index 65%
rename from packages/nocodb/src/lib/meta/api/kanbanViewApis.ts
rename to packages/nocodb/src/lib/controllers/kanbanViewController.ts
index c8d864bf82..bf36b43b90 100644
--- a/packages/nocodb/src/lib/meta/api/kanbanViewApis.ts
+++ b/packages/nocodb/src/lib/controllers/kanbanViewController.ts
@@ -1,18 +1,19 @@
import { Request, Response, Router } from 'express';
import { KanbanType, ViewTypes } from 'nocodb-sdk';
-import View from '../../models/View';
-import KanbanView from '../../models/KanbanView';
-import { Tele } from 'nc-help';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
+import View from '../models/View';
+import KanbanView from '../models/KanbanView';
+import { T } from 'nc-help';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+
+// todo: map to service
export async function kanbanViewGet(req: Request, res: Response) {
res.json(await KanbanView.get(req.params.kanbanViewId));
}
export async function kanbanViewCreate(req: Request, res) {
- Tele.emit('evt', { evt_type: 'vtable:created', show_as: 'kanban' });
+ T.emit('evt', { evt_type: 'vtable:created', show_as: 'kanban' });
const view = await View.insert({
...req.body,
// todo: sanitize
@@ -23,7 +24,7 @@ export async function kanbanViewCreate(req: Request, res) {
}
export async function kanbanViewUpdate(req, res) {
- Tele.emit('evt', { evt_type: 'view:updated', type: 'kanban' });
+ T.emit('evt', { evt_type: 'view:updated', type: 'kanban' });
res.json(await KanbanView.update(req.params.kanbanViewId, req.body));
}
@@ -32,13 +33,11 @@ const router = Router({ mergeParams: true });
router.post(
'/api/v1/db/meta/tables/:tableId/kanbans',
metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/KanbanReq'),
ncMetaAclMw(kanbanViewCreate, 'kanbanViewCreate')
);
router.patch(
'/api/v1/db/meta/kanbans/:kanbanViewId',
metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/KanbanUpdateReq'),
ncMetaAclMw(kanbanViewUpdate, 'kanbanViewUpdate')
);
router.get(
diff --git a/packages/nocodb/src/lib/meta/api/mapViewApis.ts b/packages/nocodb/src/lib/controllers/mapViewController.ts
similarity index 53%
rename from packages/nocodb/src/lib/meta/api/mapViewApis.ts
rename to packages/nocodb/src/lib/controllers/mapViewController.ts
index 5771284da4..3a982d1b03 100644
--- a/packages/nocodb/src/lib/meta/api/mapViewApis.ts
+++ b/packages/nocodb/src/lib/controllers/mapViewController.ts
@@ -1,29 +1,30 @@
import { Request, Response, Router } from 'express';
-import { MapType, ViewTypes } from 'nocodb-sdk';
-import View from '../../models/View';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { Tele } from 'nc-help';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import MapView from '../../models/MapView';
+import { MapType } from 'nocodb-sdk';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { mapViewService } from '../services';
export async function mapViewGet(req: Request, res: Response) {
- res.json(await MapView.get(req.params.mapViewId));
+ res.json(
+ await mapViewService.mapViewGet({ mapViewId: req.params.mapViewId })
+ );
}
export async function mapViewCreate(req: Request, res) {
- Tele.emit('evt', { evt_type: 'vtable:created', show_as: 'map' });
- const view = await View.insert({
- ...req.body,
- // todo: sanitize
- fk_model_id: req.params.tableId,
- type: ViewTypes.MAP,
+ const view = await mapViewService.mapViewCreate({
+ tableId: req.params.tableId,
+ map: req.body,
});
res.json(view);
}
export async function mapViewUpdate(req, res) {
- Tele.emit('evt', { evt_type: 'view:updated', type: 'map' });
- res.json(await MapView.update(req.params.mapViewId, req.body));
+ res.json(
+ await mapViewService.mapViewUpdate({
+ mapViewId: req.params.mapViewId,
+ map: req.body,
+ })
+ );
}
const router = Router({ mergeParams: true });
diff --git a/packages/nocodb/src/lib/controllers/metaDiffController.ts b/packages/nocodb/src/lib/controllers/metaDiffController.ts
new file mode 100644
index 0000000000..45ac703a0d
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/metaDiffController.ts
@@ -0,0 +1,54 @@
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { Router } from 'express';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { metaDiffService } from '../services';
+
+export async function metaDiff(req, res) {
+ res.json(await metaDiffService.metaDiff({ projectId: req.params.projectId }));
+}
+
+export async function baseMetaDiff(req, res) {
+ res.json(
+ await metaDiffService.baseMetaDiff({
+ baseId: req.params.baseId,
+ projectId: req.params.projectId,
+ })
+ );
+}
+
+export async function metaDiffSync(req, res) {
+ await metaDiffService.metaDiffSync({ projectId: req.params.projectId });
+ res.json({ msg: 'success' });
+}
+
+export async function baseMetaDiffSync(req, res) {
+ await metaDiffService.baseMetaDiffSync({
+ projectId: req.params.projectId,
+ baseId: req.params.baseId,
+ });
+
+ res.json({ msg: 'success' });
+}
+
+const router = Router();
+router.get(
+ '/api/v1/db/meta/projects/:projectId/meta-diff',
+ metaApiMetrics,
+ ncMetaAclMw(metaDiff, 'metaDiff')
+);
+router.post(
+ '/api/v1/db/meta/projects/:projectId/meta-diff',
+ metaApiMetrics,
+ ncMetaAclMw(metaDiffSync, 'metaDiffSync')
+);
+router.get(
+ '/api/v1/db/meta/projects/:projectId/meta-diff/:baseId',
+ metaApiMetrics,
+ ncMetaAclMw(baseMetaDiff, 'baseMetaDiff')
+);
+router.post(
+ '/api/v1/db/meta/projects/:projectId/meta-diff/:baseId',
+ metaApiMetrics,
+ ncMetaAclMw(baseMetaDiffSync, 'baseMetaDiffSync')
+);
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/modelVisibilityController.ts b/packages/nocodb/src/lib/controllers/modelVisibilityController.ts
new file mode 100644
index 0000000000..6af9a71506
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/modelVisibilityController.ts
@@ -0,0 +1,34 @@
+import { Router } from 'express';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { modelVisibilityService } from '../services';
+
+async function xcVisibilityMetaSetAll(req, res) {
+ await modelVisibilityService.xcVisibilityMetaSetAll({
+ visibilityRule: req.body,
+ projectId: req.params.projectId,
+ });
+
+ res.json({ msg: 'success' });
+}
+
+const router = Router({ mergeParams: true });
+router.get(
+ '/api/v1/db/meta/projects/:projectId/visibility-rules',
+ metaApiMetrics,
+ ncMetaAclMw(async (req, res) => {
+ res.json(
+ await modelVisibilityService.xcVisibilityMetaGet({
+ projectId: req.params.projectId,
+ includeM2M:
+ req.query.includeM2M === true || req.query.includeM2M === 'true',
+ })
+ );
+ }, 'modelVisibilityList')
+);
+router.post(
+ '/api/v1/db/meta/projects/:projectId/visibility-rules',
+ metaApiMetrics,
+ ncMetaAclMw(xcVisibilityMetaSetAll, 'modelVisibilitySet')
+);
+export default router;
diff --git a/packages/nocodb/src/lib/meta/api/orgLicenseApis.ts b/packages/nocodb/src/lib/controllers/orgLicenseController.ts
similarity index 54%
rename from packages/nocodb/src/lib/meta/api/orgLicenseApis.ts
rename to packages/nocodb/src/lib/controllers/orgLicenseController.ts
index ec13f4f9e7..23e5948ba1 100644
--- a/packages/nocodb/src/lib/meta/api/orgLicenseApis.ts
+++ b/packages/nocodb/src/lib/controllers/orgLicenseController.ts
@@ -1,21 +1,15 @@
import { Router } from 'express';
import { OrgUserRoles } from 'nocodb-sdk';
-import { NC_LICENSE_KEY } from '../../constants';
-import Store from '../../models/Store';
-import Noco from '../../Noco';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { getAjvValidatorMw } from './helpers';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { orgLicenseService } from '../services';
async function licenseGet(_req, res) {
- const license = await Store.get(NC_LICENSE_KEY);
-
- res.json({ key: license?.value });
+ res.json(await orgLicenseService.licenseGet());
}
async function licenseSet(req, res) {
- await Store.saveOrUpdate({ value: req.body.key, key: NC_LICENSE_KEY });
- await Noco.loadEEState();
+ await orgLicenseService.licenseSet({ key: req.body.key });
res.json({ msg: 'License key saved' });
}
@@ -31,7 +25,6 @@ router.get(
router.post(
'/api/v1/license',
metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/LicenseReq'),
ncMetaAclMw(licenseSet, 'licenseSet', {
allowedRoles: [OrgUserRoles.SUPER_ADMIN],
blockApiTokenAccess: true,
diff --git a/packages/nocodb/src/lib/controllers/orgTokenController.ts b/packages/nocodb/src/lib/controllers/orgTokenController.ts
new file mode 100644
index 0000000000..75e406fc42
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/orgTokenController.ts
@@ -0,0 +1,63 @@
+import { Request, Response, Router } from 'express';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { getConditionalHandler } from '../meta/helpers/getHandler';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { orgTokenService, orgTokenServiceEE } from '../services';
+
+async function apiTokenList(req, res) {
+ res.json(
+ await getConditionalHandler(
+ orgTokenService.apiTokenList,
+ orgTokenServiceEE.apiTokenListEE
+ )({
+ query: req.query,
+ user: req['user'],
+ })
+ );
+}
+
+export async function apiTokenCreate(req: Request, res: Response) {
+ res.json(
+ await orgTokenService.apiTokenCreate({
+ apiToken: req.body,
+ user: req['user'],
+ })
+ );
+}
+
+export async function apiTokenDelete(req: Request, res: Response) {
+ res.json(
+ await orgTokenService.apiTokenDelete({
+ token: req.params.token,
+ user: req['user'],
+ })
+ );
+}
+
+const router = Router({ mergeParams: true });
+
+router.get(
+ '/api/v1/tokens',
+ metaApiMetrics,
+ ncMetaAclMw(apiTokenList, 'apiTokenList', {
+ // allowedRoles: [OrgUserRoles.SUPER],
+ blockApiTokenAccess: true,
+ })
+);
+router.post(
+ '/api/v1/tokens',
+ metaApiMetrics,
+ ncMetaAclMw(apiTokenCreate, 'apiTokenCreate', {
+ // allowedRoles: [OrgUserRoles.SUPER],
+ blockApiTokenAccess: true,
+ })
+);
+router.delete(
+ '/api/v1/tokens/:token',
+ metaApiMetrics,
+ ncMetaAclMw(apiTokenDelete, 'apiTokenDelete', {
+ // allowedRoles: [OrgUserRoles.SUPER],
+ blockApiTokenAccess: true,
+ })
+);
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/orgUserController.ts b/packages/nocodb/src/lib/controllers/orgUserController.ts
new file mode 100644
index 0000000000..e2e6df488a
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/orgUserController.ts
@@ -0,0 +1,154 @@
+import { Router } from 'express';
+import { OrgUserRoles } from 'nocodb-sdk';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { orgUserService } from '../services';
+
+async function userList(req, res) {
+ res.json(
+ await orgUserService.userList({
+ query: req.query,
+ })
+ );
+}
+
+async function userUpdate(req, res) {
+ res.json(
+ await orgUserService.userUpdate({
+ user: req.body,
+ userId: req.params.userId,
+ })
+ );
+}
+
+async function userDelete(req, res) {
+ await orgUserService.userDelete({
+ userId: req.params.userId,
+ });
+ res.json({ msg: 'success' });
+}
+
+async function userAdd(req, res) {
+ const result = await orgUserService.userAdd({
+ user: req.body,
+ req,
+ projectId: req.params.projectId,
+ });
+
+ res.json(result);
+}
+
+async function userSettings(_req, res): Promise {
+ await orgUserService.userSettings({});
+ res.json({});
+}
+
+async function userInviteResend(req, res): Promise {
+ await orgUserService.userInviteResend({
+ userId: req.params.userId,
+ req,
+ });
+
+ res.json({ msg: 'success' });
+}
+
+async function generateResetUrl(req, res) {
+ const result = await orgUserService.generateResetUrl({
+ siteUrl: req.ncSiteUrl,
+ userId: req.params.userId,
+ });
+
+ res.json(result);
+}
+
+async function appSettingsGet(_req, res) {
+ const settings = await orgUserService.appSettingsGet();
+ res.json(settings);
+}
+
+async function appSettingsSet(req, res) {
+ await orgUserService.appSettingsSet({
+ settings: req.body,
+ });
+
+ res.json({ msg: 'Settings saved' });
+}
+
+const router = Router({ mergeParams: true });
+router.get(
+ '/api/v1/users',
+ metaApiMetrics,
+ ncMetaAclMw(userList, 'userList', {
+ allowedRoles: [OrgUserRoles.SUPER_ADMIN],
+ blockApiTokenAccess: true,
+ })
+);
+router.patch(
+ '/api/v1/users/:userId',
+ metaApiMetrics,
+ ncMetaAclMw(userUpdate, 'userUpdate', {
+ allowedRoles: [OrgUserRoles.SUPER_ADMIN],
+ blockApiTokenAccess: true,
+ })
+);
+router.delete(
+ '/api/v1/users/:userId',
+ metaApiMetrics,
+ ncMetaAclMw(userDelete, 'userDelete', {
+ allowedRoles: [OrgUserRoles.SUPER_ADMIN],
+ blockApiTokenAccess: true,
+ })
+);
+router.post(
+ '/api/v1/users',
+ metaApiMetrics,
+ ncMetaAclMw(userAdd, 'userAdd', {
+ allowedRoles: [OrgUserRoles.SUPER_ADMIN],
+ blockApiTokenAccess: true,
+ })
+);
+router.post(
+ '/api/v1/users/settings',
+ metaApiMetrics,
+ ncMetaAclMw(userSettings, 'userSettings', {
+ allowedRoles: [OrgUserRoles.SUPER_ADMIN],
+ blockApiTokenAccess: true,
+ })
+);
+router.post(
+ '/api/v1/users/:userId/resend-invite',
+ metaApiMetrics,
+ ncMetaAclMw(userInviteResend, 'userInviteResend', {
+ allowedRoles: [OrgUserRoles.SUPER_ADMIN],
+ blockApiTokenAccess: true,
+ })
+);
+
+router.post(
+ '/api/v1/users/:userId/generate-reset-url',
+ metaApiMetrics,
+ ncMetaAclMw(generateResetUrl, 'generateResetUrl', {
+ allowedRoles: [OrgUserRoles.SUPER_ADMIN],
+ blockApiTokenAccess: true,
+ })
+);
+
+router.get(
+ '/api/v1/app-settings',
+ metaApiMetrics,
+ ncMetaAclMw(appSettingsGet, 'appSettingsGet', {
+ allowedRoles: [OrgUserRoles.SUPER_ADMIN],
+ blockApiTokenAccess: true,
+ })
+);
+
+router.post(
+ '/api/v1/app-settings',
+ metaApiMetrics,
+ ncMetaAclMw(appSettingsSet, 'appSettingsSet', {
+ allowedRoles: [OrgUserRoles.SUPER_ADMIN],
+ blockApiTokenAccess: true,
+ })
+);
+
+export default router;
diff --git a/packages/nocodb/src/lib/meta/api/pluginApis.ts b/packages/nocodb/src/lib/controllers/pluginController.ts
similarity index 54%
rename from packages/nocodb/src/lib/meta/api/pluginApis.ts
rename to packages/nocodb/src/lib/controllers/pluginController.ts
index 6934e17b41..8d3e1b6943 100644
--- a/packages/nocodb/src/lib/meta/api/pluginApis.ts
+++ b/packages/nocodb/src/lib/controllers/pluginController.ts
@@ -1,38 +1,35 @@
import { Request, Response, Router } from 'express';
-import { Tele } from 'nc-help';
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-import Plugin from '../../models/Plugin';
+import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import { PluginType } from 'nocodb-sdk';
-import NcPluginMgrv2 from '../helpers/NcPluginMgrv2';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { pluginService } from '../services';
export async function pluginList(_req: Request, res: Response) {
- res.json(new PagedResponseImpl(await Plugin.list()));
+ res.json(new PagedResponseImpl(await pluginService.pluginList()));
}
export async function pluginTest(req: Request, res: Response) {
- Tele.emit('evt', { evt_type: 'plugin:tested' });
- res.json(await NcPluginMgrv2.test(req.body));
+ res.json(await pluginService.pluginTest({ body: req.body }));
}
export async function pluginRead(req: Request, res: Response) {
- res.json(await Plugin.get(req.params.pluginId));
+ res.json(await pluginService.pluginRead({ pluginId: req.params.pluginId }));
}
export async function pluginUpdate(
req: Request,
res: Response
) {
- const plugin = await Plugin.update(req.params.pluginId, req.body);
- Tele.emit('evt', {
- evt_type: plugin.active ? 'plugin:installed' : 'plugin:uninstalled',
- title: plugin.title,
+ const plugin = await pluginService.pluginUpdate({
+ pluginId: req.params.pluginId,
+ plugin: req.body,
});
res.json(plugin);
}
export async function isPluginActive(req: Request, res: Response) {
- res.json(await Plugin.isPluginActive(req.params.pluginTitle));
+ res.json(
+ await pluginService.isPluginActive({ pluginTitle: req.params.pluginTitle })
+ );
}
const router = Router({ mergeParams: true });
@@ -44,8 +41,6 @@ router.get(
router.post(
'/api/v1/db/meta/plugins/test',
metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/PluginTestReq'),
-
ncMetaAclMw(pluginTest, 'pluginTest')
);
router.get(
@@ -56,7 +51,6 @@ router.get(
router.patch(
'/api/v1/db/meta/plugins/:pluginId',
metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/PluginReq'),
ncMetaAclMw(pluginUpdate, 'pluginUpdate')
);
router.get(
diff --git a/packages/nocodb/src/lib/controllers/projectController.ts b/packages/nocodb/src/lib/controllers/projectController.ts
new file mode 100644
index 0000000000..18d761efb1
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/projectController.ts
@@ -0,0 +1,169 @@
+import { Request, Response } from 'express';
+import { ProjectType } from 'nocodb-sdk';
+import Project from '../models/Project';
+import { ProjectListType } from 'nocodb-sdk';
+import { packageVersion } from '../utils/packageVersion';
+import { T } from 'nc-help';
+import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
+import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import ProjectUser from '../models/ProjectUser';
+import Noco from '../Noco';
+import isDocker from 'is-docker';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import Filter from '../models/Filter';
+
+import { projectService } from '../services';
+
+// // Project CRUD
+
+export async function projectGet(
+ req: Request,
+ res: Response
+) {
+ const project = await projectService.getProjectWithInfo({
+ projectId: req.params.projectId,
+ });
+
+ projectService.sanitizeProject(project);
+
+ res.json(project);
+}
+
+export async function projectUpdate(
+ req: Request,
+ res: Response
+) {
+ const project = await projectService.projectUpdate({
+ projectId: req.params.projectId,
+ project: req.body,
+ });
+
+ res.json(project);
+}
+
+export async function projectList(
+ req: Request & { user: { id: string; roles: string } },
+ res: Response
+) {
+ const projects = await projectService.projectList({
+ user: req.user,
+ query: req.query,
+ });
+
+ res.json(
+ new PagedResponseImpl(projects as ProjectType[], {
+ count: projects.length,
+ limit: projects.length,
+ })
+ );
+}
+
+export async function projectDelete(req: Request, res: Response) {
+ const deleted = await projectService.projectSoftDelete({
+ projectId: req.params.projectId,
+ });
+
+ res.json(deleted);
+}
+
+async function projectCreate(req: Request, res) {
+ const project = await projectService.projectCreate({
+ project: req.body,
+ user: req['user'],
+ });
+
+ res.json(project);
+}
+
+export async function projectInfoGet(_req, res) {
+ res.json({
+ Node: process.version,
+ Arch: process.arch,
+ Platform: process.platform,
+ Docker: isDocker(),
+ RootDB: Noco.getConfig()?.meta?.db?.client,
+ PackageVersion: packageVersion,
+ });
+}
+
+export async function projectCost(req, res) {
+ let cost = 0;
+ const project = await Project.getWithInfo(req.params.projectId);
+
+ for (const base of project.bases) {
+ const sqlClient = await NcConnectionMgrv2.getSqlClient(base);
+ const userCount = await ProjectUser.getUsersCount(req.query);
+ const recordCount = (await sqlClient.totalRecords())?.data.TotalRecords;
+
+ if (recordCount > 100000) {
+ // 36,000 or $79/user/month
+ cost = Math.max(36000, 948 * userCount);
+ } else if (recordCount > 50000) {
+ // $36,000 or $50/user/month
+ cost = Math.max(36000, 600 * userCount);
+ } else if (recordCount > 10000) {
+ // $240/user/yr
+ cost = Math.min(240 * userCount, 36000);
+ } else if (recordCount > 1000) {
+ // $120/user/yr
+ cost = Math.min(120 * userCount, 36000);
+ }
+ }
+
+ T.event({
+ event: 'a:project:cost',
+ data: {
+ cost,
+ },
+ });
+
+ res.json({ cost });
+}
+
+export async function hasEmptyOrNullFilters(req, res) {
+ res.json(await Filter.hasEmptyOrNullFilters(req.params.projectId));
+}
+
+export default (router) => {
+ router.get(
+ '/api/v1/db/meta/projects/:projectId/info',
+ metaApiMetrics,
+ ncMetaAclMw(projectInfoGet, 'projectInfoGet')
+ );
+ router.get(
+ '/api/v1/db/meta/projects/:projectId',
+ metaApiMetrics,
+ ncMetaAclMw(projectGet, 'projectGet')
+ );
+ router.patch(
+ '/api/v1/db/meta/projects/:projectId',
+ metaApiMetrics,
+ ncMetaAclMw(projectUpdate, 'projectUpdate')
+ );
+ router.get(
+ '/api/v1/db/meta/projects/:projectId/cost',
+ metaApiMetrics,
+ ncMetaAclMw(projectCost, 'projectCost')
+ );
+ router.delete(
+ '/api/v1/db/meta/projects/:projectId',
+ metaApiMetrics,
+ ncMetaAclMw(projectDelete, 'projectDelete')
+ );
+ router.post(
+ '/api/v1/db/meta/projects',
+ metaApiMetrics,
+ ncMetaAclMw(projectCreate, 'projectCreate')
+ );
+ router.get(
+ '/api/v1/db/meta/projects',
+ metaApiMetrics,
+ ncMetaAclMw(projectList, 'projectList')
+ );
+ router.get(
+ '/api/v1/db/meta/projects/:projectId/has-empty-or-null-filters',
+ metaApiMetrics,
+ ncMetaAclMw(hasEmptyOrNullFilters, 'hasEmptyOrNullFilters')
+ );
+};
diff --git a/packages/nocodb/src/lib/controllers/projectUserController.ts b/packages/nocodb/src/lib/controllers/projectUserController.ts
new file mode 100644
index 0000000000..f2bace15a4
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/projectUserController.ts
@@ -0,0 +1,85 @@
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { Router } from 'express';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { projectUserService } from '../services';
+
+async function userList(req, res) {
+ res.json({
+ users: await projectUserService.userList({
+ projectId: req.params.projectId,
+ query: req.query,
+ }),
+ });
+}
+
+async function userInvite(req, res): Promise {
+ res.json(
+ await projectUserService.userInvite({
+ projectId: req.params.projectId,
+ projectUser: req.body,
+ req,
+ })
+ );
+}
+
+// @ts-ignore
+async function projectUserUpdate(req, res, next): Promise {
+ res.json(
+ await projectUserService.projectUserUpdate({
+ projectUser: req.body,
+ projectId: req.params.projectId,
+ userId: req.params.userId,
+ req,
+ })
+ );
+}
+
+async function projectUserDelete(req, res): Promise {
+ await projectUserService.projectUserDelete({
+ projectId: req.params.projectId,
+ userId: req.params.userId,
+ req,
+ });
+ res.json({
+ msg: 'success',
+ });
+}
+
+async function projectUserInviteResend(req, res): Promise {
+ res.json(
+ await projectUserService.projectUserInviteResend({
+ projectId: req.params.projectId,
+ userId: req.params.userId,
+ projectUser: req.body,
+ req,
+ })
+ );
+}
+
+const router = Router({ mergeParams: true });
+router.get(
+ '/api/v1/db/meta/projects/:projectId/users',
+ metaApiMetrics,
+ ncMetaAclMw(userList, 'userList')
+);
+router.post(
+ '/api/v1/db/meta/projects/:projectId/users',
+ metaApiMetrics,
+ ncMetaAclMw(userInvite, 'userInvite')
+);
+router.patch(
+ '/api/v1/db/meta/projects/:projectId/users/:userId',
+ metaApiMetrics,
+ ncMetaAclMw(projectUserUpdate, 'projectUserUpdate')
+);
+router.delete(
+ '/api/v1/db/meta/projects/:projectId/users/:userId',
+ metaApiMetrics,
+ ncMetaAclMw(projectUserDelete, 'projectUserDelete')
+);
+router.post(
+ '/api/v1/db/meta/projects/:projectId/users/:userId/resend-invite',
+ metaApiMetrics,
+ ncMetaAclMw(projectUserInviteResend, 'projectUserInviteResend')
+);
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/publicControllers/index.ts b/packages/nocodb/src/lib/controllers/publicControllers/index.ts
new file mode 100644
index 0000000000..1d63952955
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/publicControllers/index.ts
@@ -0,0 +1,9 @@
+import publicDataController from './publicDataController';
+import publicDataExportController from './publicDataExportController';
+import publicMetaController from './publicMetaController';
+
+export {
+ publicDataController,
+ publicDataExportController,
+ publicMetaController,
+};
diff --git a/packages/nocodb/src/lib/controllers/publicControllers/publicDataController.ts b/packages/nocodb/src/lib/controllers/publicControllers/publicDataController.ts
new file mode 100644
index 0000000000..46a73a0f2a
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/publicControllers/publicDataController.ts
@@ -0,0 +1,105 @@
+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 = await publicDataService.dataList({
+ query: req.query,
+ password: req.headers?.['xc-password'] as string,
+ sharedViewUuid: req.params.sharedViewUuid,
+ });
+ res.json({ data: 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 = await 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;
diff --git a/packages/nocodb/src/lib/meta/api/publicApis/publicDataExportApis.ts b/packages/nocodb/src/lib/controllers/publicControllers/publicDataExportController.ts
similarity index 92%
rename from packages/nocodb/src/lib/meta/api/publicApis/publicDataExportApis.ts
rename to packages/nocodb/src/lib/controllers/publicControllers/publicDataExportController.ts
index 936cc68c9b..cc677d6ab6 100644
--- a/packages/nocodb/src/lib/meta/api/publicApis/publicDataExportApis.ts
+++ b/packages/nocodb/src/lib/controllers/publicControllers/publicDataExportController.ts
@@ -1,17 +1,19 @@
import { Request, Response, Router } from 'express';
import * as XLSX from 'xlsx';
-import View from '../../../models/View';
-import Model from '../../../models/Model';
-import Base from '../../../models/Base';
-import NcConnectionMgrv2 from '../../../utils/common/NcConnectionMgrv2';
import { nocoExecute } from 'nc-help';
import papaparse from 'papaparse';
import { ErrorMessages, isSystemColumn, UITypes, ViewTypes } from 'nocodb-sdk';
-import Column from '../../../models/Column';
-import LinkToAnotherRecordColumn from '../../../models/LinkToAnotherRecordColumn';
-import LookupColumn from '../../../models/LookupColumn';
-import catchError, { NcError } from '../../helpers/catchError';
-import getAst from '../../../db/sql-data-mapper/lib/sql/helpers/getAst';
+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);
diff --git a/packages/nocodb/src/lib/controllers/publicControllers/publicMetaController.ts b/packages/nocodb/src/lib/controllers/publicControllers/publicMetaController.ts
new file mode 100644
index 0000000000..238e3223ae
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/publicControllers/publicMetaController.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 {
+ res.json(
+ await publicMetaService.publicSharedBaseGet({
+ sharedBaseUuid: req.params.sharedBaseUuid,
+ })
+ );
+}
+
+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;
diff --git a/packages/nocodb/src/lib/controllers/sharedBaseController.ts b/packages/nocodb/src/lib/controllers/sharedBaseController.ts
new file mode 100644
index 0000000000..1fd9fb27cd
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/sharedBaseController.ts
@@ -0,0 +1,61 @@
+import { Router } from 'express';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { sharedBaseService } from '../services';
+
+async function createSharedBaseLink(req, res): Promise {
+ const sharedBase = await sharedBaseService.createSharedBaseLink({
+ projectId: req.params.projectId,
+ roles: req.body?.roles,
+ password: req.body?.password,
+ siteUrl: req.ncSiteUrl,
+ });
+
+ res.json(sharedBase);
+}
+
+async function updateSharedBaseLink(req, res): Promise {
+ const sharedBase = await sharedBaseService.updateSharedBaseLink({
+ projectId: req.params.projectId,
+ roles: req.body?.roles,
+ password: req.body?.password,
+ siteUrl: req.ncSiteUrl,
+ });
+
+ res.json(sharedBase);
+}
+
+async function disableSharedBaseLink(req, res): Promise {
+ const sharedBase = await sharedBaseService.disableSharedBaseLink({
+ projectId: req.params.projectId,
+ });
+
+ res.json(sharedBase);
+}
+
+async function getSharedBaseLink(req, res): Promise {
+ const sharedBase = await sharedBaseService.getSharedBaseLink({
+ projectId: req.params.projectId,
+ siteUrl: req.ncSiteUrl,
+ });
+
+ res.json(sharedBase);
+}
+
+const router = Router({ mergeParams: true });
+router.get(
+ '/api/v1/db/meta/projects/:projectId/shared',
+ ncMetaAclMw(getSharedBaseLink, 'getSharedBaseLink')
+);
+router.post(
+ '/api/v1/db/meta/projects/:projectId/shared',
+ ncMetaAclMw(createSharedBaseLink, 'createSharedBaseLink')
+);
+router.patch(
+ '/api/v1/db/meta/projects/:projectId/shared',
+ ncMetaAclMw(updateSharedBaseLink, 'updateSharedBaseLink')
+);
+router.delete(
+ '/api/v1/db/meta/projects/:projectId/shared',
+ ncMetaAclMw(disableSharedBaseLink, 'disableSharedBaseLink')
+);
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/sortController.ts b/packages/nocodb/src/lib/controllers/sortController.ts
new file mode 100644
index 0000000000..332e31e419
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/sortController.ts
@@ -0,0 +1,80 @@
+import { Request, Response, Router } from 'express';
+import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
+import { SortListType, SortReqType } from 'nocodb-sdk';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+
+import { sortService } from '../services';
+
+// @ts-ignore
+export async function sortList(
+ req: Request,
+ res: Response
+) {
+ const sortList = await sortService.sortList({
+ viewId: req.params.viewId,
+ });
+ res.json({
+ sorts: new PagedResponseImpl(sortList),
+ });
+}
+
+// @ts-ignore
+export async function sortCreate(req: Request, res) {
+ const sort = await sortService.sortCreate({
+ sort: req.body,
+ viewId: req.params.viewId,
+ });
+ res.json(sort);
+}
+
+export async function sortUpdate(req, res) {
+ const sort = await sortService.sortUpdate({
+ sortId: req.params.sortId,
+ sort: req.body,
+ });
+ res.json(sort);
+}
+
+export async function sortDelete(req: Request, res: Response) {
+ const sort = await sortService.sortDelete({
+ sortId: req.params.sortId,
+ });
+ res.json(sort);
+}
+export async function sortGet(req: Request, res: Response) {
+ const sort = await sortService.sortGet({
+ sortId: req.params.sortId,
+ });
+ res.json(sort);
+}
+
+const router = Router({ mergeParams: true });
+router.get(
+ '/api/v1/db/meta/views/:viewId/sorts/',
+ metaApiMetrics,
+ ncMetaAclMw(sortList, 'sortList')
+);
+router.post(
+ '/api/v1/db/meta/views/:viewId/sorts/',
+ metaApiMetrics,
+ ncMetaAclMw(sortCreate, 'sortCreate')
+);
+
+router.get(
+ '/api/v1/db/meta/sorts/:sortId',
+ metaApiMetrics,
+ ncMetaAclMw(sortGet, 'sortGet')
+);
+
+router.patch(
+ '/api/v1/db/meta/sorts/:sortId',
+ metaApiMetrics,
+ ncMetaAclMw(sortUpdate, 'sortUpdate')
+);
+router.delete(
+ '/api/v1/db/meta/sorts/:sortId',
+ metaApiMetrics,
+ ncMetaAclMw(sortDelete, 'sortDelete')
+);
+export default router;
diff --git a/packages/nocodb/src/lib/controllers/swaggerController/index.ts b/packages/nocodb/src/lib/controllers/swaggerController/index.ts
new file mode 100644
index 0000000000..1853e4c7ff
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/swaggerController/index.ts
@@ -0,0 +1,36 @@
+import { Router } from 'express';
+import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw';
+import getSwaggerHtml from './swaggerHtml';
+import getRedocHtml from './redocHtml';
+import { swaggerService } from '../../services';
+
+async function swaggerJson(req, res) {
+ const swagger = await swaggerService.swaggerJson({
+ projectId: req.params.projectId,
+ siteUrl: req.ncSiteUrl,
+ });
+
+ res.json(swagger);
+}
+
+function swaggerHtml(_, res) {
+ res.send(getSwaggerHtml({ ncSiteUrl: process.env.NC_PUBLIC_URL || '' }));
+}
+
+function redocHtml(_, res) {
+ res.send(getRedocHtml({ ncSiteUrl: process.env.NC_PUBLIC_URL || '' }));
+}
+
+const router = Router({ mergeParams: true });
+
+// todo: auth
+router.get(
+ '/api/v1/db/meta/projects/:projectId/swagger.json',
+ ncMetaAclMw(swaggerJson, 'swaggerJson')
+);
+
+router.get('/api/v1/db/meta/projects/:projectId/swagger', swaggerHtml);
+
+router.get('/api/v1/db/meta/projects/:projectId/redoc', redocHtml);
+
+export default router;
diff --git a/packages/nocodb/src/lib/meta/api/swagger/redocHtml.ts b/packages/nocodb/src/lib/controllers/swaggerController/redocHtml.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/swagger/redocHtml.ts
rename to packages/nocodb/src/lib/controllers/swaggerController/redocHtml.ts
diff --git a/packages/nocodb/src/lib/meta/api/swagger/swaggerHtml.ts b/packages/nocodb/src/lib/controllers/swaggerController/swaggerHtml.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/swagger/swaggerHtml.ts
rename to packages/nocodb/src/lib/controllers/swaggerController/swaggerHtml.ts
diff --git a/packages/nocodb/src/lib/meta/api/sync/importApis.ts b/packages/nocodb/src/lib/controllers/syncController/importApis.ts
similarity index 87%
rename from packages/nocodb/src/lib/meta/api/sync/importApis.ts
rename to packages/nocodb/src/lib/controllers/syncController/importApis.ts
index 92ab241117..f161a40ada 100644
--- a/packages/nocodb/src/lib/meta/api/sync/importApis.ts
+++ b/packages/nocodb/src/lib/controllers/syncController/importApis.ts
@@ -1,13 +1,14 @@
import { Request, Router } from 'express';
// import { Queue } from 'bullmq';
// import axios from 'axios';
-import catchError, { NcError } from '../../helpers/catchError';
+import catchError, { NcError } from '../../meta/helpers/catchError';
import { Server } from 'socket.io';
-import NocoJobs from '../../../jobs/NocoJobs';
-import job, { AirtableSyncConfig } from './helpers/job';
-import SyncSource from '../../../models/SyncSource';
-import Noco from '../../../Noco';
-import { genJwt } from '../userApi/helpers';
+import NocoJobs from '../../jobs/NocoJobs';
+import { SyncSource } from '../../models';
+import Noco from '../../Noco';
+import { syncService, userService } from '../../services';
+import { AirtableSyncConfig } from '../../services/syncService/helpers/job';
+
const AIRTABLE_IMPORT_JOB = 'AIRTABLE_IMPORT_JOB';
const AIRTABLE_PROGRESS_JOB = 'AIRTABLE_PROGRESS_JOB';
@@ -23,7 +24,10 @@ export default (
jobs: { [id: string]: { last_message: any } }
) => {
// add importer job handler and progress notification job handler
- NocoJobs.jobsMgr.addJobWorker(AIRTABLE_IMPORT_JOB, job);
+ NocoJobs.jobsMgr.addJobWorker(
+ AIRTABLE_IMPORT_JOB,
+ syncService.airtableImportJob
+ );
NocoJobs.jobsMgr.addJobWorker(
AIRTABLE_PROGRESS_JOB,
({ payload, progress }) => {
@@ -94,7 +98,7 @@ export default (
const syncSource = await SyncSource.get(req.params.syncId);
const user = await syncSource.getUser();
- const token = genJwt(user, Noco.getConfig());
+ const token = userService.genJwt(user, Noco.getConfig());
// Treat default baseUrl as siteUrl from req object
let baseURL = (req as any).ncSiteUrl;
diff --git a/packages/nocodb/src/lib/meta/api/sync/syncSourceApis.ts b/packages/nocodb/src/lib/controllers/syncController/index.ts
similarity index 53%
rename from packages/nocodb/src/lib/meta/api/sync/syncSourceApis.ts
rename to packages/nocodb/src/lib/controllers/syncController/index.ts
index f9e761ff71..7dbfac49b5 100644
--- a/packages/nocodb/src/lib/meta/api/sync/syncSourceApis.ts
+++ b/packages/nocodb/src/lib/controllers/syncController/index.ts
@@ -1,42 +1,42 @@
import { Request, Response, Router } from 'express';
-
-import SyncSource from '../../../models/SyncSource';
-import { Tele } from 'nc-help';
-import { PagedResponseImpl } from '../../helpers/PagedResponse';
-import ncMetaAclMw from '../../helpers/ncMetaAclMw';
-import Project from '../../../models/Project';
+import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw';
+import { syncService } from '../../services';
export async function syncSourceList(req: Request, res: Response) {
// todo: pagination
res.json(
- new PagedResponseImpl(
- await SyncSource.list(req.params.projectId, req.params.baseId)
- )
+ syncService.syncSourceList({
+ projectId: req.params.projectId,
+ })
);
}
export async function syncCreate(req: Request, res: Response) {
- Tele.emit('evt', { evt_type: 'webhooks:created' });
- const project = await Project.getWithInfo(req.params.projectId);
-
- const sync = await SyncSource.insert({
- ...req.body,
- fk_user_id: (req as any).user.id,
- base_id: req.params.baseId ? req.params.baseId : project.bases[0].id,
- project_id: req.params.projectId,
- });
- res.json(sync);
+ res.json(
+ await syncService.syncCreate({
+ projectId: req.params.projectId,
+ baseId: req.params.baseId,
+ userId: (req as any).user.id,
+ syncPayload: req.body,
+ })
+ );
}
export async function syncDelete(req: Request, res: Response) {
- Tele.emit('evt', { evt_type: 'webhooks:deleted' });
- res.json(await SyncSource.delete(req.params.syncId));
+ res.json(
+ await syncService.syncDelete({
+ syncId: req.params.syncId,
+ })
+ );
}
export async function syncUpdate(req: Request, res: Response) {
- Tele.emit('evt', { evt_type: 'webhooks:updated' });
-
- res.json(await SyncSource.update(req.params.syncId, req.body));
+ res.json(
+ await syncService.syncUpdate({
+ syncId: req.params.syncId,
+ syncPayload: req.body,
+ })
+ );
}
const router = Router({ mergeParams: true });
diff --git a/packages/nocodb/src/lib/controllers/tableController.ts b/packages/nocodb/src/lib/controllers/tableController.ts
new file mode 100644
index 0000000000..c59c8d4a13
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/tableController.ts
@@ -0,0 +1,111 @@
+import { Request, Response, Router } from 'express';
+import { TableListType, TableReqType, TableType } from 'nocodb-sdk';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
+import { tableService } from '../services';
+
+export async function tableList(req: Request, res: Response) {
+ res.json(
+ new PagedResponseImpl(
+ await tableService.getAccessibleTables({
+ projectId: req.params.projectId,
+ baseId: req.params.baseId,
+ includeM2M: req.query?.includeM2M === 'true',
+ roles: (req as any).session?.passport?.user?.roles,
+ })
+ )
+ );
+}
+
+export async function tableCreate(req: Request, res) {
+ const result = await tableService.tableCreate({
+ projectId: req.params.projectId,
+ baseId: req.params.baseId,
+ table: req.body,
+ user: (req as any).session?.passport?.user,
+ });
+
+ res.json(result);
+}
+
+export async function tableGet(req: Request, res: Response) {
+ const table = await tableService.getTableWithAccessibleViews({
+ tableId: req.params.tableId,
+ user: (req as any).session?.passport?.user,
+ });
+
+ res.json(table);
+}
+
+export async function tableDelete(req: Request, res: Response) {
+ const result = await tableService.tableDelete({
+ tableId: req.params.tableId,
+ user: (req as any).session?.passport?.user,
+ req,
+ });
+
+ res.json(result);
+}
+
+export async function tableReorder(req: Request, res: Response) {
+ res.json(
+ await tableService.reorderTable({
+ tableId: req.params.tableId,
+ order: req.body.order,
+ })
+ );
+}
+
+// todo: move to table service
+export async function tableUpdate(req: Request, res) {
+ await tableService.tableUpdate({
+ tableId: req.params.tableId,
+ table: req.body,
+ projectId: (req as any).ncProjectId,
+ });
+ res.json({ msg: 'success' });
+}
+
+const router = Router({ mergeParams: true });
+router.get(
+ '/api/v1/db/meta/projects/:projectId/tables',
+ metaApiMetrics,
+ ncMetaAclMw(tableList, 'tableList')
+);
+router.get(
+ '/api/v1/db/meta/projects/:projectId/:baseId/tables',
+ metaApiMetrics,
+ ncMetaAclMw(tableList, 'tableList')
+);
+router.post(
+ '/api/v1/db/meta/projects/:projectId/tables',
+ 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,
+ ncMetaAclMw(tableGet, 'tableGet')
+);
+router.patch(
+ '/api/v1/db/meta/tables/:tableId',
+ metaApiMetrics,
+ ncMetaAclMw(tableUpdate, 'tableUpdate')
+);
+router.delete(
+ '/api/v1/db/meta/tables/:tableId',
+ metaApiMetrics,
+ ncMetaAclMw(tableDelete, 'tableDelete')
+);
+router.post(
+ '/api/v1/db/meta/tables/:tableId/reorder',
+ metaApiMetrics,
+ ncMetaAclMw(tableReorder, 'tableReorder')
+);
+export default router;
diff --git a/packages/nocodb/src/lib/meta/api/testApis.ts b/packages/nocodb/src/lib/controllers/testController.ts
similarity index 85%
rename from packages/nocodb/src/lib/meta/api/testApis.ts
rename to packages/nocodb/src/lib/controllers/testController.ts
index 506f9d4474..08ea1e780e 100644
--- a/packages/nocodb/src/lib/meta/api/testApis.ts
+++ b/packages/nocodb/src/lib/controllers/testController.ts
@@ -1,5 +1,5 @@
import { Request, Router } from 'express';
-import { TestResetService } from '../../services/test/TestResetService';
+import { TestResetService } from '../services/test/TestResetService';
export async function reset(req: Request, res) {
const service = new TestResetService({
diff --git a/packages/nocodb/src/lib/controllers/userController/index.ts b/packages/nocodb/src/lib/controllers/userController/index.ts
new file mode 100644
index 0000000000..8843b2e5ea
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/userController/index.ts
@@ -0,0 +1,445 @@
+import { Request, Response } from 'express';
+import { TableType, validatePassword } from 'nocodb-sdk';
+import { T } from 'nc-help';
+
+const { isEmail } = require('validator');
+import * as ejs from 'ejs';
+
+import bcrypt from 'bcryptjs';
+import { promisify } from 'util';
+
+const { v4: uuidv4 } = require('uuid');
+
+import passport from 'passport';
+import { getAjvValidatorMw } from '../../meta/api/helpers';
+import catchError, { NcError } from '../../meta/helpers/catchError';
+import extractProjectIdAndAuthenticate from '../../meta/helpers/extractProjectIdAndAuthenticate';
+import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw';
+import NcPluginMgrv2 from '../../meta/helpers/NcPluginMgrv2';
+import { Audit, User } from '../../models';
+import Noco from '../../Noco';
+import { userService } from '../../services';
+
+export async function signup(req: Request, res: Response) {
+ const {
+ email: _email,
+ firstname,
+ lastname,
+ token,
+ ignore_subscribe,
+ } = req.body;
+ let { password } = req.body;
+
+ // validate password and throw error if password is satisfying the conditions
+ const { valid, error } = validatePassword(password);
+ if (!valid) {
+ NcError.badRequest(`Password : ${error}`);
+ }
+
+ if (!isEmail(_email)) {
+ NcError.badRequest(`Invalid email`);
+ }
+
+ const email = _email.toLowerCase();
+
+ let user = await User.getByEmail(email);
+
+ if (user) {
+ if (token) {
+ if (token !== user.invite_token) {
+ NcError.badRequest(`Invalid invite url`);
+ } else if (user.invite_token_expires < new Date()) {
+ NcError.badRequest(
+ 'Expired invite url, Please contact super admin to get a new invite url'
+ );
+ }
+ } else {
+ // todo : opening up signup for timebeing
+ // return next(new Error(`Email '${email}' already registered`));
+ }
+ }
+
+ const salt = await promisify(bcrypt.genSalt)(10);
+ password = await promisify(bcrypt.hash)(password, salt);
+ const email_verification_token = uuidv4();
+
+ if (!ignore_subscribe) {
+ T.emit('evt_subscribe', email);
+ }
+
+ if (user) {
+ if (token) {
+ await User.update(user.id, {
+ firstname,
+ lastname,
+ salt,
+ password,
+ email_verification_token,
+ invite_token: null,
+ invite_token_expires: null,
+ email: user.email,
+ });
+ } else {
+ NcError.badRequest('User already exist');
+ }
+ } else {
+ await userService.registerNewUserIfAllowed({
+ firstname,
+ lastname,
+ email,
+ salt,
+ password,
+ email_verification_token,
+ });
+ }
+ user = await User.getByEmail(email);
+
+ try {
+ const template = (await import('./ui/emailTemplates/verify')).default;
+ await (
+ await NcPluginMgrv2.emailAdapter()
+ ).mailSend({
+ to: email,
+ subject: 'Verify email',
+ html: ejs.render(template, {
+ verifyLink:
+ (req as any).ncSiteUrl +
+ `/email/verify/${user.email_verification_token}`,
+ }),
+ });
+ } catch (e) {
+ console.log(
+ 'Warning : `mailSend` failed, Please configure emailClient configuration.'
+ );
+ }
+ await promisify((req as any).login.bind(req))(user);
+ const refreshToken = userService.randomTokenString();
+ await User.update(user.id, {
+ refresh_token: refreshToken,
+ email: user.email,
+ });
+
+ setTokenCookie(res, refreshToken);
+
+ user = (req as any).user;
+
+ await Audit.insert({
+ op_type: 'AUTHENTICATION',
+ op_sub_type: 'SIGNUP',
+ user: user.email,
+ description: `signed up `,
+ ip: (req as any).clientIp,
+ });
+
+ res.json({
+ token: userService.genJwt(user, Noco.getConfig()),
+ } as any);
+}
+
+async function successfulSignIn({
+ user,
+ err,
+ info,
+ req,
+ res,
+ auditDescription,
+}) {
+ try {
+ if (!user || !user.email) {
+ if (err) {
+ return res.status(400).send(err);
+ }
+ if (info) {
+ return res.status(400).send(info);
+ }
+ return res.status(400).send({ msg: 'Your signin has failed' });
+ }
+
+ await promisify((req as any).login.bind(req))(user);
+ const refreshToken = userService.randomTokenString();
+
+ if (!user.token_version) {
+ user.token_version = userService.randomTokenString();
+ }
+
+ await User.update(user.id, {
+ refresh_token: refreshToken,
+ email: user.email,
+ token_version: user.token_version,
+ });
+ setTokenCookie(res, refreshToken);
+
+ await Audit.insert({
+ op_type: 'AUTHENTICATION',
+ op_sub_type: 'SIGNIN',
+ user: user.email,
+ ip: req.clientIp,
+ description: auditDescription,
+ });
+
+ res.json({
+ token: userService.genJwt(user, Noco.getConfig()),
+ } as any);
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+}
+
+async function signin(req, res, next) {
+ passport.authenticate(
+ 'local',
+ { session: false },
+ async (err, user, info): Promise =>
+ await successfulSignIn({
+ user,
+ err,
+ info,
+ req,
+ res,
+ auditDescription: 'signed in',
+ })
+ )(req, res, next);
+}
+
+async function googleSignin(req, res, next) {
+ passport.authenticate(
+ 'google',
+ {
+ session: false,
+ callbackURL: req.ncSiteUrl + Noco.getConfig().dashboardPath,
+ },
+ async (err, user, info): Promise =>
+ await successfulSignIn({
+ user,
+ err,
+ info,
+ req,
+ res,
+ auditDescription: 'signed in using Google Auth',
+ })
+ )(req, res, next);
+}
+
+function setTokenCookie(res: Response, token): void {
+ // create http only cookie with refresh token that expires in 7 days
+ const cookieOptions = {
+ httpOnly: true,
+ expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
+ };
+ res.cookie('refresh_token', token, cookieOptions);
+}
+
+async function me(req, res): Promise {
+ res.json(req?.session?.passport?.user ?? {});
+}
+
+async function passwordChange(req: Request, res): Promise {
+ if (!(req as any).isAuthenticated()) {
+ NcError.forbidden('Not allowed');
+ }
+
+ await userService.passwordChange({
+ user: req['user'],
+ req,
+ body: req.body,
+ });
+
+ res.json({ msg: 'Password updated successfully' });
+}
+
+async function passwordForgot(req: Request, res): Promise {
+ await userService.passwordForgot({
+ siteUrl: (req as any).ncSiteUrl,
+ body: req.body,
+ req,
+ });
+
+ res.json({ msg: 'Please check your email to reset the password' });
+}
+
+async function tokenValidate(req, res): Promise {
+ await userService.tokenValidate({
+ token: req.params.tokenId,
+ });
+ res.json(true);
+}
+
+async function passwordReset(req, res): Promise {
+ await userService.passwordReset({
+ token: req.params.tokenId,
+ body: req.body,
+ req,
+ });
+
+ res.json({ msg: 'Password reset successful' });
+}
+
+async function emailVerification(req, res): Promise {
+ await userService.emailVerification({
+ token: req.params.tokenId,
+ req,
+ });
+
+ res.json({ msg: 'Email verified successfully' });
+}
+
+async function refreshToken(req, res): Promise {
+ try {
+ if (!req?.cookies?.refresh_token) {
+ return res.status(400).json({ msg: 'Missing refresh token' });
+ }
+
+ const user = await User.getByRefreshToken(req.cookies.refresh_token);
+
+ if (!user) {
+ return res.status(400).json({ msg: 'Invalid refresh token' });
+ }
+
+ const refreshToken = userService.randomTokenString();
+
+ await User.update(user.id, {
+ email: user.email,
+ refresh_token: refreshToken,
+ });
+
+ setTokenCookie(res, refreshToken);
+
+ res.json({
+ token: userService.genJwt(user, Noco.getConfig()),
+ } as any);
+ } catch (e) {
+ return res.status(400).json({ msg: e.message });
+ }
+}
+
+async function renderPasswordReset(req, res): Promise {
+ try {
+ res.send(
+ ejs.render((await import('./ui/auth/resetPassword')).default, {
+ ncPublicUrl: process.env.NC_PUBLIC_URL || '',
+ token: JSON.stringify(req.params.tokenId),
+ baseUrl: `/`,
+ })
+ );
+ } catch (e) {
+ return res.status(400).json({ msg: e.message });
+ }
+}
+
+const mapRoutes = (router) => {
+ // todo: old api - /auth/signup?tool=1
+ router.post(
+ '/auth/user/signup',
+ getAjvValidatorMw('swagger.json#/components/schemas/SignUpReq'),
+ catchError(signup)
+ );
+ router.post(
+ '/auth/user/signin',
+ getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'),
+ catchError(signin)
+ );
+ router.get('/auth/user/me', extractProjectIdAndAuthenticate, catchError(me));
+ router.post('/auth/password/forgot', catchError(passwordForgot));
+ router.post('/auth/token/validate/:tokenId', catchError(tokenValidate));
+ router.post(
+ '/auth/password/reset/:tokenId',
+ getAjvValidatorMw('swagger.json#/components/schemas/PasswordResetReq'),
+ catchError(passwordReset)
+ );
+ router.post('/auth/email/validate/:tokenId', catchError(emailVerification));
+ router.post(
+ '/user/password/change',
+ ncMetaAclMw(passwordChange, 'passwordChange')
+ );
+ router.post('/auth/token/refresh', catchError(refreshToken));
+
+ /* Google auth apis */
+
+ router.post(`/auth/google/genTokenByCode`, catchError(googleSignin));
+
+ router.get('/auth/google', (req: any, res, next) =>
+ passport.authenticate('google', {
+ scope: ['profile', 'email'],
+ state: req.query.state,
+ callbackURL: req.ncSiteUrl + Noco.getConfig().dashboardPath,
+ })(req, res, next)
+ );
+
+ // deprecated APIs
+ router.post(
+ '/api/v1/db/auth/user/signup',
+ getAjvValidatorMw('swagger.json#/components/schemas/SignUpReq'),
+ catchError(signup)
+ );
+ router.post(
+ '/api/v1/db/auth/user/signin',
+ getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'),
+ catchError(signin)
+ );
+ router.get(
+ '/api/v1/db/auth/user/me',
+ extractProjectIdAndAuthenticate,
+ catchError(me)
+ );
+ router.post('/api/v1/db/auth/password/forgot', catchError(passwordForgot));
+ router.post(
+ '/api/v1/db/auth/token/validate/:tokenId',
+ catchError(tokenValidate)
+ );
+ router.post(
+ '/api/v1/db/auth/password/reset/:tokenId',
+ catchError(passwordReset)
+ );
+ router.post(
+ '/api/v1/db/auth/email/validate/:tokenId',
+ catchError(emailVerification)
+ );
+ router.post(
+ '/api/v1/db/auth/password/change',
+ ncMetaAclMw(passwordChange, 'passwordChange')
+ );
+ router.post('/api/v1/db/auth/token/refresh', catchError(refreshToken));
+ router.get(
+ '/api/v1/db/auth/password/reset/:tokenId',
+ catchError(renderPasswordReset)
+ );
+
+ // new API
+ router.post(
+ '/api/v1/auth/user/signup',
+ getAjvValidatorMw('swagger.json#/components/schemas/SignUpReq'),
+ catchError(signup)
+ );
+ router.post(
+ '/api/v1/auth/user/signin',
+ getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'),
+ catchError(signin)
+ );
+ router.get(
+ '/api/v1/auth/user/me',
+ extractProjectIdAndAuthenticate,
+ catchError(me)
+ );
+ router.post('/api/v1/auth/password/forgot', catchError(passwordForgot));
+ router.post(
+ '/api/v1/auth/token/validate/:tokenId',
+ catchError(tokenValidate)
+ );
+ router.post(
+ '/api/v1/auth/password/reset/:tokenId',
+ catchError(passwordReset)
+ );
+ router.post(
+ '/api/v1/auth/email/validate/:tokenId',
+ catchError(emailVerification)
+ );
+ router.post(
+ '/api/v1/auth/password/change',
+ ncMetaAclMw(passwordChange, 'passwordChange')
+ );
+ router.post('/api/v1/auth/token/refresh', catchError(refreshToken));
+ // respond with password reset page
+ router.get('/auth/password/reset/:tokenId', catchError(renderPasswordReset));
+};
+export { mapRoutes as userController };
diff --git a/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts b/packages/nocodb/src/lib/controllers/userController/initStrategies.ts
similarity index 95%
rename from packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts
rename to packages/nocodb/src/lib/controllers/userController/initStrategies.ts
index b40f2c58d1..3b6b99c9aa 100644
--- a/packages/nocodb/src/lib/meta/api/userApi/initStrategies.ts
+++ b/packages/nocodb/src/lib/controllers/userController/initStrategies.ts
@@ -1,6 +1,4 @@
import { OrgUserRoles } from 'nocodb-sdk';
-import User from '../../../models/User';
-import ProjectUser from '../../../models/ProjectUser';
import { promisify } from 'util';
import { Strategy as CustomStrategy } from 'passport-custom';
import passport from 'passport';
@@ -17,13 +15,11 @@ const jwtOptions = {
};
import bcrypt from 'bcryptjs';
-import Project from '../../../models/Project';
-import NocoCache from '../../../cache/NocoCache';
-import { CacheGetType, CacheScope } from '../../../utils/globals';
-import ApiToken from '../../../models/ApiToken';
-import Noco from '../../../Noco';
-import Plugin from '../../../models/Plugin';
-import { registerNewUserIfAllowed } from './userApis';
+import NocoCache from '../../cache/NocoCache';
+import { ApiToken, Plugin, Project, ProjectUser, User } from '../../models';
+import Noco from '../../Noco';
+import { CacheGetType, CacheScope } from '../../utils/globals';
+import { userService } from '../../services';
export function initStrategies(router): void {
passport.use(
@@ -311,7 +307,7 @@ export function initStrategies(router): void {
// or return error
} else {
const salt = await promisify(bcrypt.genSalt)(10);
- const user = await registerNewUserIfAllowed({
+ const user = await userService.registerNewUserIfAllowed({
firstname: null,
lastname: null,
email_verification_token: null,
diff --git a/packages/nocodb/src/lib/meta/api/userApi/ui/auth/emailVerify.ts b/packages/nocodb/src/lib/controllers/userController/ui/auth/emailVerify.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/userApi/ui/auth/emailVerify.ts
rename to packages/nocodb/src/lib/controllers/userController/ui/auth/emailVerify.ts
diff --git a/packages/nocodb/src/lib/meta/api/userApi/ui/auth/resetPassword.ts b/packages/nocodb/src/lib/controllers/userController/ui/auth/resetPassword.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/userApi/ui/auth/resetPassword.ts
rename to packages/nocodb/src/lib/controllers/userController/ui/auth/resetPassword.ts
diff --git a/packages/nocodb/src/lib/meta/api/userApi/ui/emailTemplates/forgotPassword.ts b/packages/nocodb/src/lib/controllers/userController/ui/emailTemplates/forgotPassword.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/userApi/ui/emailTemplates/forgotPassword.ts
rename to packages/nocodb/src/lib/controllers/userController/ui/emailTemplates/forgotPassword.ts
diff --git a/packages/nocodb/src/lib/meta/api/userApi/ui/emailTemplates/invite.ts b/packages/nocodb/src/lib/controllers/userController/ui/emailTemplates/invite.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/userApi/ui/emailTemplates/invite.ts
rename to packages/nocodb/src/lib/controllers/userController/ui/emailTemplates/invite.ts
diff --git a/packages/nocodb/src/lib/meta/api/userApi/ui/emailTemplates/verify.ts b/packages/nocodb/src/lib/controllers/userController/ui/emailTemplates/verify.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/userApi/ui/emailTemplates/verify.ts
rename to packages/nocodb/src/lib/controllers/userController/ui/emailTemplates/verify.ts
diff --git a/packages/nocodb/src/lib/meta/api/userApi/userApis.ts b/packages/nocodb/src/lib/controllers/userController/userApis.ts
similarity index 57%
rename from packages/nocodb/src/lib/meta/api/userApi/userApis.ts
rename to packages/nocodb/src/lib/controllers/userController/userApis.ts
index 809b96a6c1..70d17b40ef 100644
--- a/packages/nocodb/src/lib/meta/api/userApi/userApis.ts
+++ b/packages/nocodb/src/lib/controllers/userController/userApis.ts
@@ -1,82 +1,24 @@
import { Request, Response } from 'express';
import { TableType, validatePassword } from 'nocodb-sdk';
-import { OrgUserRoles } from 'nocodb-sdk';
-import { NC_APP_SETTINGS } from '../../../constants';
-import Store from '../../../models/Store';
-import { Tele } from 'nc-help';
-import catchError, { NcError } from '../../helpers/catchError';
+import { T } from 'nc-help';
const { isEmail } = require('validator');
import * as ejs from 'ejs';
import bcrypt from 'bcryptjs';
import { promisify } from 'util';
-import User from '../../../models/User';
const { v4: uuidv4 } = require('uuid');
-import Audit from '../../../models/Audit';
-import NcPluginMgrv2 from '../../helpers/NcPluginMgrv2';
import passport from 'passport';
-import extractProjectIdAndAuthenticate from '../../helpers/extractProjectIdAndAuthenticate';
-import ncMetaAclMw from '../../helpers/ncMetaAclMw';
-import { MetaTable } from '../../../utils/globals';
-import Noco from '../../../Noco';
-import { getAjvValidatorMw } from '../helpers';
-import { genJwt } from './helpers';
-import { randomTokenString } from '../../helpers/stringHelpers';
-
-export async function registerNewUserIfAllowed({
- firstname,
- lastname,
- email,
- salt,
- password,
- email_verification_token,
-}: {
- firstname;
- lastname;
- email: string;
- salt: any;
- password;
- email_verification_token;
-}) {
- let roles: string = OrgUserRoles.CREATOR;
-
- if (await User.isFirst()) {
- roles = `${OrgUserRoles.CREATOR},${OrgUserRoles.SUPER_ADMIN}`;
- // todo: update in nc_store
- // roles = 'owner,creator,editor'
- Tele.emit('evt', {
- evt_type: 'project:invite',
- count: 1,
- });
- } else {
- let settings: { invite_only_signup?: boolean } = {};
- try {
- settings = JSON.parse((await Store.get(NC_APP_SETTINGS))?.value);
- } catch {}
-
- if (settings?.invite_only_signup) {
- NcError.badRequest('Not allowed to signup, contact super admin.');
- } else {
- roles = OrgUserRoles.VIEWER;
- }
- }
-
- const token_version = randomTokenString();
-
- return await User.insert({
- firstname,
- lastname,
- email,
- salt,
- password,
- email_verification_token,
- roles,
- token_version,
- });
-}
+import { getAjvValidatorMw } from '../../meta/api/helpers';
+import catchError, { NcError } from '../../meta/helpers/catchError';
+import extractProjectIdAndAuthenticate from '../../meta/helpers/extractProjectIdAndAuthenticate';
+import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw';
+import NcPluginMgrv2 from '../../meta/helpers/NcPluginMgrv2';
+import { Audit, User } from '../../models';
+import Noco from '../../Noco';
+import { userService } from '../../services';
export async function signup(req: Request, res: Response) {
const {
@@ -122,7 +64,7 @@ export async function signup(req: Request, res: Response) {
const email_verification_token = uuidv4();
if (!ignore_subscribe) {
- Tele.emit('evt_subscribe', email);
+ T.emit('evt_subscribe', email);
}
if (user) {
@@ -141,7 +83,7 @@ export async function signup(req: Request, res: Response) {
NcError.badRequest('User already exist');
}
} else {
- await registerNewUserIfAllowed({
+ await userService.registerNewUserIfAllowed({
firstname,
lastname,
email,
@@ -171,7 +113,7 @@ export async function signup(req: Request, res: Response) {
);
}
await promisify((req as any).login.bind(req))(user);
- const refreshToken = randomTokenString();
+ const refreshToken = userService.randomTokenString();
await User.update(user.id, {
refresh_token: refreshToken,
email: user.email,
@@ -190,7 +132,7 @@ export async function signup(req: Request, res: Response) {
});
res.json({
- token: genJwt(user, Noco.getConfig()),
+ token: userService.genJwt(user, Noco.getConfig()),
} as any);
}
@@ -214,10 +156,10 @@ async function successfulSignIn({
}
await promisify((req as any).login.bind(req))(user);
- const refreshToken = randomTokenString();
+ const refreshToken = userService.randomTokenString();
if (!user.token_version) {
- user.token_version = randomTokenString();
+ user.token_version = userService.randomTokenString();
}
await User.update(user.id, {
@@ -236,7 +178,7 @@ async function successfulSignIn({
});
res.json({
- token: genJwt(user, Noco.getConfig()),
+ token: userService.genJwt(user, Noco.getConfig()),
} as any);
} catch (e) {
console.log(e);
@@ -279,15 +221,13 @@ async function googleSignin(req, res, next) {
)(req, res, next);
}
-const REFRESH_TOKEN_COOKIE_KEY = 'refresh_token';
-
-function setTokenCookie(res, token): void {
+function setTokenCookie(res: Response, token): void {
// create http only cookie with refresh token that expires in 7 days
const cookieOptions = {
httpOnly: true,
expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
};
- res.cookie(REFRESH_TOKEN_COOKIE_KEY, token, cookieOptions);
+ res.cookie('refresh_token', token, cookieOptions);
}
async function me(req, res): Promise {
@@ -298,184 +238,47 @@ async function passwordChange(req: Request, res): Promise {
if (!(req as any).isAuthenticated()) {
NcError.forbidden('Not allowed');
}
- const { currentPassword, newPassword } = req.body;
- if (!currentPassword || !newPassword) {
- return NcError.badRequest('Missing new/old password');
- }
-
- // validate password and throw error if password is satisfying the conditions
- const { valid, error } = validatePassword(newPassword);
- if (!valid) {
- NcError.badRequest(`Password : ${error}`);
- }
-
- const user = await User.getByEmail((req as any).user.email);
- const hashedPassword = await promisify(bcrypt.hash)(
- currentPassword,
- user.salt
- );
- if (hashedPassword !== user.password) {
- return NcError.badRequest('Current password is wrong');
- }
-
- const salt = await promisify(bcrypt.genSalt)(10);
- const password = await promisify(bcrypt.hash)(newPassword, salt);
- await User.update(user.id, {
- salt,
- password,
- email: user.email,
- token_version: null,
- });
-
- await Audit.insert({
- op_type: 'AUTHENTICATION',
- op_sub_type: 'PASSWORD_CHANGE',
- user: user.email,
- description: `changed password `,
- ip: (req as any).clientIp,
+ await userService.passwordChange({
+ user: req['user'],
+ req,
+ body: req.body,
});
res.json({ msg: 'Password updated successfully' });
}
async function passwordForgot(req: Request, res): Promise {
- const _email = req.body.email;
- if (!_email) {
- NcError.badRequest('Please enter your email address.');
- }
-
- const email = _email.toLowerCase();
- const user = await User.getByEmail(email);
-
- if (user) {
- const token = uuidv4();
- await User.update(user.id, {
- email: user.email,
- reset_password_token: token,
- reset_password_expires: new Date(Date.now() + 60 * 60 * 1000),
- token_version: null,
- });
- try {
- const template = (await import('./ui/emailTemplates/forgotPassword'))
- .default;
- await NcPluginMgrv2.emailAdapter().then((adapter) =>
- adapter.mailSend({
- to: user.email,
- subject: 'Password Reset Link',
- text: `Visit following link to update your password : ${
- (req as any).ncSiteUrl
- }/auth/password/reset/${token}.`,
- html: ejs.render(template, {
- resetLink: (req as any).ncSiteUrl + `/auth/password/reset/${token}`,
- }),
- })
- );
- } catch (e) {
- console.log(e);
- return NcError.badRequest(
- 'Email Plugin is not found. Please contact administrators to configure it in App Store first.'
- );
- }
+ await userService.passwordForgot({
+ siteUrl: (req as any).ncSiteUrl,
+ body: req.body,
+ req,
+ });
- await Audit.insert({
- op_type: 'AUTHENTICATION',
- op_sub_type: 'PASSWORD_FORGOT',
- user: user.email,
- description: `requested for password reset `,
- ip: (req as any).clientIp,
- });
- } else {
- return NcError.badRequest('Your email has not been registered.');
- }
res.json({ msg: 'Please check your email to reset the password' });
}
async function tokenValidate(req, res): Promise {
- const token = req.params.tokenId;
-
- const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, {
- reset_password_token: token,
+ await userService.tokenValidate({
+ token: req.params.tokenId,
});
-
- if (!user || !user.email) {
- NcError.badRequest('Invalid reset url');
- }
- if (new Date(user.reset_password_expires) < new Date()) {
- NcError.badRequest('Password reset url expired');
- }
res.json(true);
}
async function passwordReset(req, res): Promise {
- const token = req.params.tokenId;
-
- const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, {
- reset_password_token: token,
- });
-
- if (!user) {
- NcError.badRequest('Invalid reset url');
- }
- if (user.reset_password_expires < new Date()) {
- NcError.badRequest('Password reset url expired');
- }
- if (user.provider && user.provider !== 'local') {
- NcError.badRequest('Email registered via social account');
- }
-
- // validate password and throw error if password is satisfying the conditions
- const { valid, error } = validatePassword(req.body.password);
- if (!valid) {
- NcError.badRequest(`Password : ${error}`);
- }
-
- const salt = await promisify(bcrypt.genSalt)(10);
- const password = await promisify(bcrypt.hash)(req.body.password, salt);
-
- await User.update(user.id, {
- salt,
- password,
- email: user.email,
- reset_password_expires: null,
- reset_password_token: '',
- token_version: null,
- });
-
- await Audit.insert({
- op_type: 'AUTHENTICATION',
- op_sub_type: 'PASSWORD_RESET',
- user: user.email,
- description: `did reset password `,
- ip: req.clientIp,
+ await userService.passwordReset({
+ token: req.params.tokenId,
+ body: req.body,
+ req,
});
res.json({ msg: 'Password reset successful' });
}
async function emailVerification(req, res): Promise {
- const token = req.params.tokenId;
-
- const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, {
- email_verification_token: token,
- });
-
- if (!user) {
- NcError.badRequest('Invalid verification url');
- }
-
- await User.update(user.id, {
- email: user.email,
- email_verification_token: '',
- email_verified: true,
- });
-
- await Audit.insert({
- op_type: 'AUTHENTICATION',
- op_sub_type: 'EMAIL_VERIFICATION',
- user: user.email,
- description: `verified email `,
- ip: req.clientIp,
+ await userService.emailVerification({
+ token: req.params.tokenId,
+ req,
});
res.json({ msg: 'Email verified successfully' });
@@ -487,15 +290,13 @@ async function refreshToken(req, res): Promise {
return res.status(400).json({ msg: 'Missing refresh token' });
}
- const user = await User.getByRefreshToken(
- req.cookies[REFRESH_TOKEN_COOKIE_KEY]
- );
+ const user = await User.getByRefreshToken(req.cookies.refresh_token);
if (!user) {
return res.status(400).json({ msg: 'Invalid refresh token' });
}
- const refreshToken = randomTokenString();
+ const refreshToken = userService.randomTokenString();
await User.update(user.id, {
email: user.email,
@@ -505,7 +306,7 @@ async function refreshToken(req, res): Promise {
setTokenCookie(res, refreshToken);
res.json({
- token: genJwt(user, Noco.getConfig()),
+ token: userService.genJwt(user, Noco.getConfig()),
} as any);
} catch (e) {
return res.status(400).json({ msg: e.message });
@@ -526,30 +327,6 @@ async function renderPasswordReset(req, res): Promise {
}
}
-// clear refresh token cookie and update user refresh token to null
-const signout = async (req, res): Promise => {
- const resBody = { msg: 'Success' };
- if (!req.cookies[REFRESH_TOKEN_COOKIE_KEY]) {
- return res.json(resBody);
- }
-
- const user = await User.getByRefreshToken(
- req.cookies[REFRESH_TOKEN_COOKIE_KEY]
- );
-
- if (!user) {
- return res.json(resBody);
- }
-
- res.clearCookie(REFRESH_TOKEN_COOKIE_KEY);
-
- await User.update(user.id, {
- refresh_token: null,
- });
-
- res.json(resBody);
-};
-
const mapRoutes = (router) => {
// todo: old api - /auth/signup?tool=1
router.post(
@@ -562,7 +339,6 @@ const mapRoutes = (router) => {
getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'),
catchError(signin)
);
- router.post('/auth/user/signout', catchError(signout));
router.get('/auth/user/me', extractProjectIdAndAuthenticate, catchError(me));
router.post(
'/auth/password/forgot',
@@ -651,7 +427,6 @@ const mapRoutes = (router) => {
getAjvValidatorMw('swagger.json#/components/schemas/SignInReq'),
catchError(signin)
);
- router.post('/api/v1/auth/user/signout', catchError(signout));
router.get(
'/api/v1/auth/user/me',
extractProjectIdAndAuthenticate,
@@ -683,4 +458,4 @@ const mapRoutes = (router) => {
// respond with password reset page
router.get('/auth/password/reset/:tokenId', catchError(renderPasswordReset));
};
-export { mapRoutes as userApis };
+export { mapRoutes as userController };
diff --git a/packages/nocodb/src/lib/controllers/utilController.ts b/packages/nocodb/src/lib/controllers/utilController.ts
new file mode 100644
index 0000000000..e5e2b504b6
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/utilController.ts
@@ -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')
+ );
+};
diff --git a/packages/nocodb/src/lib/meta/api/viewColumnApis.ts b/packages/nocodb/src/lib/controllers/viewColumnController.ts
similarity index 50%
rename from packages/nocodb/src/lib/meta/api/viewColumnApis.ts
rename to packages/nocodb/src/lib/controllers/viewColumnController.ts
index 9da4a8c51d..aece1ca06e 100644
--- a/packages/nocodb/src/lib/meta/api/viewColumnApis.ts
+++ b/packages/nocodb/src/lib/controllers/viewColumnController.ts
@@ -1,34 +1,30 @@
import { Request, Response, Router } from 'express';
-import View from '../../models/View';
-import { Tele } from 'nc-help';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { viewColumnService } from '../services';
export async function columnList(req: Request, res: Response) {
- res.json(await View.getColumns(req.params.viewId));
+ res.json(await viewColumnService.columnList({ viewId: req.params.viewId }));
}
+
export async function columnAdd(req: Request, res: Response) {
- const viewColumn = await View.insertOrUpdateColumn(
- req.params.viewId,
- req.body.fk_column_id,
- {
+ const viewColumn = await viewColumnService.columnAdd({
+ viewId: req.params.viewId,
+ columnId: req.body.fk_column_id,
+ column: {
...req.body,
view_id: req.params.viewId,
- }
- );
- Tele.emit('evt', { evt_type: 'viewColumn:inserted' });
-
+ },
+ });
res.json(viewColumn);
}
export async function columnUpdate(req: Request, res: Response) {
- const result = await View.updateColumn(
- req.params.viewId,
- req.params.columnId,
- req.body
- );
- Tele.emit('evt', { evt_type: 'viewColumn:updated' });
+ const result = await viewColumnService.columnUpdate({
+ viewId: req.params.viewId,
+ columnId: req.params.columnId,
+ column: req.body,
+ });
res.json(result);
}
@@ -41,13 +37,11 @@ router.get(
router.post(
'/api/v1/db/meta/views/:viewId/columns/',
metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/ViewColumnReq'),
ncMetaAclMw(columnAdd, 'columnAdd')
);
router.patch(
'/api/v1/db/meta/views/:viewId/columns/:columnId',
metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/ViewColumnUpdateReq'),
ncMetaAclMw(columnUpdate, 'viewColumnUpdate')
);
export default router;
diff --git a/packages/nocodb/src/lib/controllers/viewController.ts b/packages/nocodb/src/lib/controllers/viewController.ts
new file mode 100644
index 0000000000..edbc2ed736
--- /dev/null
+++ b/packages/nocodb/src/lib/controllers/viewController.ts
@@ -0,0 +1,133 @@
+import { Request, Response, Router } from 'express';
+import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
+import { View } from '../models';
+import ncMetaAclMw from '../meta/helpers/ncMetaAclMw';
+import { metaApiMetrics } from '../meta/helpers/apiMetrics';
+import { viewService } from '../services';
+
+// @ts-ignore
+export async function viewGet(req: Request, res: Response) {}
+
+// @ts-ignore
+export async function viewList(req: Request, res: Response) {
+ const filteredViewList = await viewService.viewList({
+ tableId: req.params.tableId,
+ user: (req as any).session?.passport?.user,
+ });
+
+ res.json(new PagedResponseImpl(filteredViewList));
+}
+
+// @ts-ignore
+export async function shareView(
+ req: Request,
+ res: Response
+) {
+ res.json(await viewService.shareView({ viewId: req.params.viewId }));
+}
+
+// @ts-ignore
+export async function viewCreate(req: Request, res, next) {}
+
+// @ts-ignore
+export async function viewUpdate(req, res) {
+ const result = await viewService.viewUpdate({
+ viewId: req.params.viewId,
+ view: req.body,
+ });
+ res.json(result);
+}
+
+// @ts-ignore
+export async function viewDelete(req: Request, res: Response, next) {
+ const result = await viewService.viewDelete({ viewId: req.params.viewId });
+ res.json(result);
+}
+
+async function shareViewUpdate(req: Request, res) {
+ res.json(
+ await viewService.shareViewUpdate({
+ viewId: req.params.viewId,
+ sharedView: req.body,
+ })
+ );
+}
+
+async function shareViewDelete(req: Request, res) {
+ res.json(await viewService.shareViewDelete({ viewId: req.params.viewId }));
+}
+
+async function showAllColumns(req: Request, res) {
+ res.json(
+ await viewService.showAllColumns({
+ viewId: req.params.viewId,
+ ignoreIds: (req.query?.ignoreIds || []),
+ })
+ );
+}
+
+async function hideAllColumns(req: Request, res) {
+ res.json(
+ await viewService.hideAllColumns({
+ viewId: req.params.viewId,
+ ignoreIds: (req.query?.ignoreIds || []),
+ })
+ );
+}
+
+async function shareViewList(req: Request, res) {
+ res.json(
+ await viewService.shareViewList({
+ tableId: req.params.tableId,
+ })
+ );
+}
+
+const router = Router({ mergeParams: true });
+router.get(
+ '/api/v1/db/meta/tables/:tableId/views',
+ metaApiMetrics,
+ ncMetaAclMw(viewList, 'viewList')
+);
+router.patch(
+ '/api/v1/db/meta/views/:viewId',
+ metaApiMetrics,
+ ncMetaAclMw(viewUpdate, 'viewUpdate')
+);
+router.delete(
+ '/api/v1/db/meta/views/:viewId',
+ metaApiMetrics,
+ ncMetaAclMw(viewDelete, 'viewDelete')
+);
+router.post(
+ '/api/v1/db/meta/views/:viewId/show-all',
+ metaApiMetrics,
+ ncMetaAclMw(showAllColumns, 'showAllColumns')
+);
+router.post(
+ '/api/v1/db/meta/views/:viewId/hide-all',
+ metaApiMetrics,
+ ncMetaAclMw(hideAllColumns, 'hideAllColumns')
+);
+
+router.get(
+ '/api/v1/db/meta/tables/:tableId/share',
+ metaApiMetrics,
+ ncMetaAclMw(shareViewList, 'shareViewList')
+);
+router.post(
+ '/api/v1/db/meta/views/:viewId/share',
+ ncMetaAclMw(shareView, 'shareView')
+);
+router.patch(
+ '/api/v1/db/meta/views/:viewId/share',
+ metaApiMetrics,
+ ncMetaAclMw(shareViewUpdate, 'shareViewUpdate')
+);
+router.delete(
+ '/api/v1/db/meta/views/:viewId/share',
+ metaApiMetrics,
+ ncMetaAclMw(shareViewDelete, 'shareViewDelete')
+);
+
+export default router;
diff --git a/packages/nocodb/src/lib/db/sql-client/lib/KnexClient.ts b/packages/nocodb/src/lib/db/sql-client/lib/KnexClient.ts
index 9c47ab34ac..7643c92c9b 100644
--- a/packages/nocodb/src/lib/db/sql-client/lib/KnexClient.ts
+++ b/packages/nocodb/src/lib/db/sql-client/lib/KnexClient.ts
@@ -1,6 +1,6 @@
/* eslint-disable no-constant-condition */
import { knex, Knex } from 'knex';
-import { Tele } from 'nc-help';
+import { T } from 'nc-help';
import Debug from '../../util/Debug';
import Emit from '../../util/emit';
import Result from '../../util/Result';
@@ -620,7 +620,7 @@ class KnexClient extends SqlClient {
KnexClient.___ext = await this._validateInput();
}
if (!KnexClient.___ext) {
- Tele.emit('evt', {
+ T.emit('evt', {
evt_type: 'project:external',
payload: null,
check: true,
diff --git a/packages/nocodb/src/lib/db/sql-mgr/SqlMgr.ts b/packages/nocodb/src/lib/db/sql-mgr/SqlMgr.ts
index 3f6a85b9c2..55ea1559d7 100644
--- a/packages/nocodb/src/lib/db/sql-mgr/SqlMgr.ts
+++ b/packages/nocodb/src/lib/db/sql-mgr/SqlMgr.ts
@@ -7,7 +7,7 @@ import fsExtra from 'fs-extra';
import importFresh from 'import-fresh';
import inflection from 'inflection';
import slash from 'slash';
-import { Tele } from 'nc-help';
+import { T } from 'nc-help';
import SqlClientFactory from '../sql-client/lib/SqlClientFactory';
// import debug from 'debug';
@@ -1047,7 +1047,7 @@ export default class SqlMgr {
// t = process.hrtime();
const data = await require('axios')(...apiMeta);
- Tele.emit('evt', { evt_type: 'import:excel:url' });
+ T.emit('evt', { evt_type: 'import:excel:url' });
return data.data;
}
diff --git a/packages/nocodb/src/lib/meta/NcMetaMgr.ts b/packages/nocodb/src/lib/meta/NcMetaMgr.ts
index aecaff7189..da98f2c5e9 100644
--- a/packages/nocodb/src/lib/meta/NcMetaMgr.ts
+++ b/packages/nocodb/src/lib/meta/NcMetaMgr.ts
@@ -40,7 +40,7 @@ import NcTemplateParser from '../v1-legacy/templates/NcTemplateParser';
import { defaultConnectionConfig } from '../utils/NcConfigFactory';
import xcMetaDiff from './handlers/xcMetaDiff';
import { UITypes } from 'nocodb-sdk';
-import { Tele } from 'nc-help';
+import { T } from 'nc-help';
import { NC_ATTACHMENT_FIELD_SIZE } from '../constants';
const randomID = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 10);
const XC_PLUGIN_DET = 'XC_PLUGIN_DET';
@@ -953,7 +953,7 @@ export default class NcMetaMgr {
ip: req.clientIp,
});
- Tele.emit('evt', { evt_type: 'webhooks:deleted' });
+ T.emit('evt', { evt_type: 'webhooks:deleted' });
} catch (e) {
throw e;
}
@@ -988,7 +988,7 @@ export default class NcMetaMgr {
ip: req.clientIp,
});
- Tele.emit('evt', { evt_type: 'webhooks:updated' });
+ T.emit('evt', { evt_type: 'webhooks:updated' });
} else {
const res = await this.xcMeta.metaInsert(
projectId,
@@ -1009,7 +1009,7 @@ export default class NcMetaMgr {
description: `created webhook ${args.args.data.title} - ${args.args.data.event} ${args.args.data.operation} - ${args.args.data.notification?.type} - of table ${args.args.tn} `,
ip: req.clientIp,
});
- Tele.emit('evt', { evt_type: 'webhooks:created' });
+ T.emit('evt', { evt_type: 'webhooks:created' });
return res;
}
@@ -1269,7 +1269,7 @@ export default class NcMetaMgr {
} catch (e) {
throw e;
} finally {
- Tele.emit('evt', { evt_type: 'image:uploaded' });
+ T.emit('evt', { evt_type: 'image:uploaded' });
}
}
@@ -1595,7 +1595,7 @@ export default class NcMetaMgr {
ip: req.clientIp,
});
- Tele.emit('evt', { evt_type: 'project:created' });
+ T.emit('evt', { evt_type: 'project:created' });
break;
case 'projectUpdateByWeb':
@@ -1603,7 +1603,7 @@ export default class NcMetaMgr {
this.getProjectId(args),
args.args.projectJson
);
- Tele.emit('evt', { evt_type: 'project:updated' });
+ T.emit('evt', { evt_type: 'project:updated' });
break;
case 'projectCreateByOneClick':
{
@@ -1635,7 +1635,7 @@ export default class NcMetaMgr {
description: `created project ${config.title}(${result.id}) `,
ip: req.clientIp,
});
- Tele.emit('evt', { evt_type: 'project:created', oneClick: true });
+ T.emit('evt', { evt_type: 'project:created', oneClick: true });
}
break;
case 'projectCreateByWebWithXCDB': {
@@ -1689,10 +1689,10 @@ export default class NcMetaMgr {
ip: req.clientIp,
});
- Tele.emit('evt', { evt_type: 'project:created', xcdb: true });
+ T.emit('evt', { evt_type: 'project:created', xcdb: true });
postListenerCb = async () => {
if (args?.args?.template) {
- Tele.emit('evt', {
+ T.emit('evt', {
evt_type: args.args?.quickImport
? 'project:created:fromExcel'
: 'project:created:fromTemplate',
@@ -1730,7 +1730,7 @@ export default class NcMetaMgr {
case 'projectDelete':
case 'projectRestart':
case 'projectStart':
- Tele.emit('evt', { evt_type: 'project:' + args.api });
+ T.emit('evt', { evt_type: 'project:' + args.api });
result = null;
break;
@@ -2145,7 +2145,7 @@ export default class NcMetaMgr {
ip: req.clientIp,
});
- Tele.emit('evt', { evt_type: 'acl:updated' });
+ T.emit('evt', { evt_type: 'acl:updated' });
return res;
} catch (e) {
@@ -3485,7 +3485,7 @@ export default class NcMetaMgr {
['id', 'view_id', 'view_type']
);
res.url = `${req.ncSiteUrl}${this.config.dashboardPath}#/nc/view/${res.view_id}`;
- Tele.emit('evt', { evt_type: 'sharedView:generated-link' });
+ T.emit('evt', { evt_type: 'sharedView:generated-link' });
return res;
} catch (e) {
console.log(e);
@@ -3549,7 +3549,7 @@ export default class NcMetaMgr {
sharedBase.url = `${req.ncSiteUrl}${this.config.dashboardPath}#/nc/base/${sharedBase.shared_base_id}`;
- Tele.emit('evt', { evt_type: 'sharedBase:generated-link' });
+ T.emit('evt', { evt_type: 'sharedBase:generated-link' });
return sharedBase;
} catch (e) {
console.log(e);
@@ -3606,7 +3606,7 @@ export default class NcMetaMgr {
// await this.xcMeta.metaUpdate(this.getProjectId(args), this.getDbAlias(args), 'nc_shared_views', {
// password: args.args?.password
// }, args.args.id);
- // Tele.emit('evt', {evt_type: 'sharedView:password-updated'})
+ // T.emit('evt', {evt_type: 'sharedView:password-updated'})
// return {msg: 'Success'};
// } catch (e) {
// console.log(e)
@@ -3623,7 +3623,7 @@ export default class NcMetaMgr {
'nc_shared_views',
args.args.id
);
- Tele.emit('evt', { evt_type: 'sharedView:deleted' });
+ T.emit('evt', { evt_type: 'sharedView:deleted' });
return { msg: 'Success' };
} catch (e) {
console.log(e);
@@ -4477,7 +4477,7 @@ export default class NcMetaMgr {
});
}
- Tele.emit('evt', { evt_type: 'template:imported' });
+ T.emit('evt', { evt_type: 'template:imported' });
return result;
}
@@ -5071,7 +5071,7 @@ export default class NcMetaMgr {
} catch (e) {
throw e;
} finally {
- Tele.emit('evt', {
+ T.emit('evt', {
evt_type: 'plugin:installed',
title: args.args.title,
});
@@ -5153,7 +5153,7 @@ export default class NcMetaMgr {
ip: req.clientIp,
});
- Tele.emit('evt', {
+ T.emit('evt', {
evt_type: 'vtable:created',
show_as: args.args.show_as,
});
@@ -5270,7 +5270,7 @@ export default class NcMetaMgr {
});
await RestAuthCtrl.instance.loadLatestApiTokens();
- Tele.emit('evt', { evt_type: 'apiToken:created' });
+ T.emit('evt', { evt_type: 'apiToken:created' });
return {
description: args.args.description,
token,
@@ -5282,7 +5282,7 @@ export default class NcMetaMgr {
}
protected async xcApiTokenDelete(args): Promise {
- Tele.emit('evt', { evt_type: 'apiToken:deleted' });
+ T.emit('evt', { evt_type: 'apiToken:deleted' });
const res = await this.xcMeta.metaDelete(
null,
null,
@@ -5327,7 +5327,7 @@ export default class NcMetaMgr {
ip: req.clientIp,
});
- Tele.emit('evt', {
+ T.emit('evt', {
evt_type: 'vtable:renamed',
show_as: args.args.show_as,
});
@@ -5335,7 +5335,7 @@ export default class NcMetaMgr {
}
protected async xcVirtualTableUpdate(args): Promise {
- // Tele.emit('evt', {evt_type: 'vtable:updated',show_as: args.args.show_as})
+ // T.emit('evt', {evt_type: 'vtable:updated',show_as: args.args.show_as})
return this.xcMeta.metaUpdate(
this.getProjectId(args),
this.getDbAlias(args),
@@ -5482,7 +5482,7 @@ export default class NcMetaMgr {
ip: req.clientIp,
});
- Tele.emit('evt', { evt_type: 'vtable:deleted' });
+ T.emit('evt', { evt_type: 'vtable:deleted' });
return res;
}
diff --git a/packages/nocodb/src/lib/meta/NcMetaMgrEE.ts b/packages/nocodb/src/lib/meta/NcMetaMgrEE.ts
index dfa8dff104..c0e10ec8ab 100644
--- a/packages/nocodb/src/lib/meta/NcMetaMgrEE.ts
+++ b/packages/nocodb/src/lib/meta/NcMetaMgrEE.ts
@@ -1,5 +1,5 @@
import { v4 as uuidv4 } from 'uuid';
-import { Tele } from 'nc-help';
+import { T } from 'nc-help';
import NcMetaMgr from './NcMetaMgr';
@@ -104,7 +104,7 @@ export default class NcMetaMgrEE extends NcMetaMgr {
ip: req.clientIp,
});
- Tele.emit('evt', { evt_type: 'acl:updated' });
+ T.emit('evt', { evt_type: 'acl:updated' });
return res;
} catch (e) {
@@ -277,7 +277,7 @@ export default class NcMetaMgrEE extends NcMetaMgr {
sharedView.url = `${req.ncSiteUrl}${this.config.dashboardPath}#/nc/view/${sharedView.view_id}`;
}
- Tele.emit('evt', { evt_type: 'sharedView:generated-link' });
+ T.emit('evt', { evt_type: 'sharedView:generated-link' });
return sharedView;
} catch (e) {
console.log(e);
@@ -295,7 +295,7 @@ export default class NcMetaMgrEE extends NcMetaMgr {
},
args.args.id
);
- Tele.emit('evt', { evt_type: 'sharedView:password-updated' });
+ T.emit('evt', { evt_type: 'sharedView:password-updated' });
return { msg: 'Success' };
} catch (e) {
console.log(e);
diff --git a/packages/nocodb/src/lib/meta/api/apiTokenApis.ts b/packages/nocodb/src/lib/meta/api/apiTokenApis.ts
deleted file mode 100644
index 5ed4c08094..0000000000
--- a/packages/nocodb/src/lib/meta/api/apiTokenApis.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { Request, Response, Router } from 'express';
-import { OrgUserRoles } from 'nocodb-sdk';
-import { Tele } from 'nc-help';
-import { NcError } from '../helpers/catchError';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import ApiToken from '../../models/ApiToken';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
-
-export async function apiTokenList(req: Request, res: Response) {
- res.json(await ApiToken.list(req['user'].id));
-}
-export async function apiTokenCreate(req: Request, res: Response) {
- Tele.emit('evt', { evt_type: 'apiToken:created' });
- res.json(await ApiToken.insert({ ...req.body, fk_user_id: req['user'].id }));
-}
-export async function apiTokenDelete(req: Request, res: Response) {
- const apiToken = await ApiToken.getByToken(req.params.token);
- if (
- !req['user'].roles.includes(OrgUserRoles.SUPER_ADMIN) &&
- apiToken.fk_user_id !== req['user'].id
- ) {
- NcError.notFound('Token not found');
- }
- Tele.emit('evt', { evt_type: 'apiToken:deleted' });
-
- // todo: verify token belongs to the user
- res.json(await ApiToken.delete(req.params.token));
-}
-
-// todo: add reset token api to regenerate token
-
-// deprecated apis
-const router = Router({ mergeParams: true });
-
-router.get(
- '/api/v1/db/meta/projects/:projectId/api-tokens',
- metaApiMetrics,
- ncMetaAclMw(apiTokenList, 'apiTokenList')
-);
-router.post(
- '/api/v1/db/meta/projects/:projectId/api-tokens',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/ApiTokenReq'),
- ncMetaAclMw(apiTokenCreate, 'apiTokenCreate')
-);
-router.delete(
- '/api/v1/db/meta/projects/:projectId/api-tokens/:token',
- metaApiMetrics,
- ncMetaAclMw(apiTokenDelete, 'apiTokenDelete')
-);
-
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/attachmentApis.ts b/packages/nocodb/src/lib/meta/api/attachmentApis.ts
deleted file mode 100644
index bfef1cd85f..0000000000
--- a/packages/nocodb/src/lib/meta/api/attachmentApis.ts
+++ /dev/null
@@ -1,225 +0,0 @@
-// @ts-ignore
-import { Request, Response, Router } from 'express';
-import multer from 'multer';
-import { nanoid } from 'nanoid';
-import { OrgUserRoles, ProjectRoles } from 'nocodb-sdk';
-import path from 'path';
-import slash from 'slash';
-import Noco from '../../Noco';
-import { MetaTable } from '../../utils/globals';
-import mimetypes, { mimeIcons } from '../../utils/mimeTypes';
-import { Tele } from 'nc-help';
-import extractProjectIdAndAuthenticate from '../helpers/extractProjectIdAndAuthenticate';
-import catchError, { NcError } from '../helpers/catchError';
-import NcPluginMgrv2 from '../helpers/NcPluginMgrv2';
-import Local from '../../v1-legacy/plugins/adapters/storage/Local';
-import { NC_ATTACHMENT_FIELD_SIZE } from '../../constants';
-import { getCacheMiddleware, getAjvValidatorMw } from './helpers';
-
-const isUploadAllowed = async (req: Request, _res: Response, next: any) => {
- if (!req['user']?.id) {
- if (!req['user']?.isPublicBase) {
- NcError.unauthorized('Unauthorized');
- }
- }
-
- try {
- // check user is super admin or creator
- if (
- req['user'].roles?.includes(OrgUserRoles.SUPER_ADMIN) ||
- req['user'].roles?.includes(OrgUserRoles.CREATOR) ||
- req['user'].roles?.includes(ProjectRoles.EDITOR) ||
- // if viewer then check at-least one project have editor or higher role
- // todo: cache
- !!(await Noco.ncMeta
- .knex(MetaTable.PROJECT_USERS)
- .where(function () {
- this.where('roles', ProjectRoles.OWNER);
- this.orWhere('roles', ProjectRoles.CREATOR);
- this.orWhere('roles', ProjectRoles.EDITOR);
- })
- .andWhere('fk_user_id', req['user'].id)
- .first())
- )
- return next();
- } catch {}
- NcError.badRequest('Upload not allowed');
-};
-
-export async function upload(req: Request, res: Response) {
- const filePath = sanitizeUrlPath(
- req.query?.path?.toString()?.split('/') || ['']
- );
- const destPath = path.join('nc', 'uploads', ...filePath);
-
- const storageAdapter = await NcPluginMgrv2.storageAdapter();
-
- const attachments = await Promise.all(
- (req as any).files?.map(async (file) => {
- const fileName = `${nanoid(18)}${path.extname(file.originalname)}`;
-
- const url = await storageAdapter.fileCreate(
- slash(path.join(destPath, fileName)),
- file
- );
-
- let attachmentPath;
-
- // if `url` is null, then it is local attachment
- if (!url) {
- // then store the attachement path only
- // url will be constructued in `useAttachmentCell`
- attachmentPath = `download/${filePath.join('/')}/${fileName}`;
- }
-
- return {
- ...(url ? { url } : {}),
- ...(attachmentPath ? { path: attachmentPath } : {}),
- title: file.originalname,
- mimetype: file.mimetype,
- size: file.size,
- icon: mimeIcons[path.extname(file.originalname).slice(1)] || undefined,
- };
- })
- );
-
- Tele.emit('evt', { evt_type: 'image:uploaded' });
-
- res.json(attachments);
-}
-
-export async function uploadViaURL(req: Request, res: Response) {
- const filePath = sanitizeUrlPath(
- req.query?.path?.toString()?.split('/') || ['']
- );
- const destPath = path.join('nc', 'uploads', ...filePath);
-
- const storageAdapter = await NcPluginMgrv2.storageAdapter();
-
- const attachments = await Promise.all(
- req.body?.map?.(async (urlMeta) => {
- const { url, fileName: _fileName } = urlMeta;
-
- const fileName = `${nanoid(18)}${_fileName || url.split('/').pop()}`;
-
- const attachmentUrl = await (storageAdapter as any).fileCreateByUrl(
- slash(path.join(destPath, fileName)),
- url
- );
-
- let attachmentPath;
-
- // if `attachmentUrl` is null, then it is local attachment
- if (!attachmentUrl) {
- // then store the attachement path only
- // url will be constructued in `useAttachmentCell`
- attachmentPath = `download/${filePath.join('/')}/${fileName}`;
- }
-
- return {
- ...(attachmentUrl ? { url: attachmentUrl } : {}),
- ...(attachmentPath ? { path: attachmentPath } : {}),
- title: fileName,
- mimetype: urlMeta.mimetype,
- size: urlMeta.size,
- icon: mimeIcons[path.extname(fileName).slice(1)] || undefined,
- };
- })
- );
-
- Tele.emit('evt', { evt_type: 'image:uploaded' });
-
- res.json(attachments);
-}
-
-export async function fileRead(req, res) {
- try {
- // get the local storage adapter to display local attachments
- const storageAdapter = new Local();
- const type =
- mimetypes[path.extname(req.params?.[0]).split('/').pop().slice(1)] ||
- 'text/plain';
-
- const img = await storageAdapter.fileRead(
- slash(
- path.join(
- 'nc',
- 'uploads',
- req.params?.[0]
- ?.split('/')
- .filter((p) => p !== '..')
- .join('/')
- )
- )
- );
- res.writeHead(200, { 'Content-Type': type });
- res.end(img, 'binary');
- } catch (e) {
- console.log(e);
- res.status(404).send('Not found');
- }
-}
-
-const router = Router({ mergeParams: true });
-
-router.get(
- /^\/dl\/([^/]+)\/([^/]+)\/(.+)$/,
- getCacheMiddleware(),
- async (req, res) => {
- try {
- // const type = mimetypes[path.extname(req.params.fileName).slice(1)] || 'text/plain';
- const type =
- mimetypes[path.extname(req.params[2]).split('/').pop().slice(1)] ||
- 'text/plain';
-
- const storageAdapter = await NcPluginMgrv2.storageAdapter();
- // const img = await this.storageAdapter.fileRead(slash(path.join('nc', req.params.projectId, req.params.dbAlias, 'uploads', req.params.fileName)));
- const img = await storageAdapter.fileRead(
- slash(
- path.join(
- 'nc',
- req.params[0],
- req.params[1],
- 'uploads',
- ...req.params[2].split('/')
- )
- )
- );
- res.writeHead(200, { 'Content-Type': type });
- res.end(img, 'binary');
- } catch (e) {
- res.status(404).send('Not found');
- }
- }
-);
-
-export function sanitizeUrlPath(paths) {
- return paths.map((url) => url.replace(/[/.?#]+/g, '_'));
-}
-
-router.post(
- '/api/v1/db/storage/upload',
- multer({
- storage: multer.diskStorage({}),
- limits: {
- fieldSize: NC_ATTACHMENT_FIELD_SIZE,
- },
- }).any(),
- [
- getAjvValidatorMw('swagger.json#/components/schemas/AttachmentReq'),
- extractProjectIdAndAuthenticate,
- catchError(isUploadAllowed),
- catchError(upload),
- ]
-);
-
-router.post('/api/v1/db/storage/upload-by-url', [
- getAjvValidatorMw('swagger.json#/components/schemas/AttachmentReq'),
- extractProjectIdAndAuthenticate,
- catchError(isUploadAllowed),
- catchError(uploadViaURL),
-]);
-
-router.get(/^\/download\/(.+)$/, getCacheMiddleware(), catchError(fileRead));
-
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/baseApis.ts b/packages/nocodb/src/lib/meta/api/baseApis.ts
deleted file mode 100644
index c13bbd18d3..0000000000
--- a/packages/nocodb/src/lib/meta/api/baseApis.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import { Request, Response } from 'express';
-import Project from '../../models/Project';
-import { BaseListType } from 'nocodb-sdk';
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-import { syncBaseMigration } from '../helpers/syncMigration';
-import Base from '../../models/Base';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { Tele } from 'nc-help';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw, populateMeta } from './helpers';
-
-export async function baseGet(
- req: Request,
- res: Response
-) {
- const base = await Base.get(req.params.baseId);
-
- base.config = base.getConnectionConfig();
-
- res.json(base);
-}
-
-export async function baseUpdate(
- req: Request,
- res: Response
-) {
- const baseBody = req.body;
- const project = await Project.getWithInfo(req.params.projectId);
- const base = await Base.updateBase(req.params.baseId, {
- ...baseBody,
- type: baseBody.config?.client,
- projectId: project.id,
- id: req.params.baseId,
- });
-
- delete base.config;
-
- Tele.emit('evt', {
- evt_type: 'base:updated',
- });
-
- res.json(base);
-}
-
-export async function baseList(
- req: Request,
- res: Response,
- next
-) {
- try {
- const bases = await Base.list({ projectId: req.params.projectId });
-
- res // todo: pagination
- .json({
- bases: new PagedResponseImpl(bases, {
- count: bases.length,
- limit: bases.length,
- }),
- });
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-
-export async function baseDelete(
- req: Request,
- res: Response
-) {
- 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, res) {
- // type | base | projectId
- const baseBody = req.body;
- const project = await Project.getWithInfo(req.params.projectId);
- const base = await Base.createBase({
- ...baseBody,
- type: baseBody.config?.client,
- projectId: project.id,
- });
-
- await syncBaseMigration(project, base);
-
- 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);
-}
-
-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,
- getAjvValidatorMw('swagger.json#/components/schemas/BaseReq'),
- 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,
- getAjvValidatorMw('swagger.json#/components/schemas/BaseReq'),
- ncMetaAclMw(baseCreate, 'baseCreate')
- );
- router.get(
- '/api/v1/db/meta/projects/:projectId/bases',
- metaApiMetrics,
- ncMetaAclMw(baseList, 'baseList')
- );
-};
diff --git a/packages/nocodb/src/lib/meta/api/dataApis/bulkDataAliasApis.ts b/packages/nocodb/src/lib/meta/api/dataApis/bulkDataAliasApis.ts
deleted file mode 100644
index 739585a1c5..0000000000
--- a/packages/nocodb/src/lib/meta/api/dataApis/bulkDataAliasApis.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { Request, Response, Router } from 'express';
-import { BaseModelSqlv2 } from '../../../db/sql-data-mapper/lib/sql/BaseModelSqlv2';
-import Model from '../../../models/Model';
-import Base from '../../../models/Base';
-import NcConnectionMgrv2 from '../../../utils/common/NcConnectionMgrv2';
-import ncMetaAclMw from '../../helpers/ncMetaAclMw';
-import { getViewAndModelFromRequestByAliasOrId } from './helpers';
-import apiMetrics from '../../helpers/apiMetrics';
-
-type BulkOperation =
- | 'bulkInsert'
- | 'bulkUpdate'
- | 'bulkUpdateAll'
- | 'bulkDelete'
- | 'bulkDeleteAll';
-
-async function getModelViewBase(req: Request) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
-
- const base = await Base.get(model.base_id);
- return { model, view, base };
-}
-
-async function executeBulkOperation(
- req: Request,
- res: Response,
- operation: T,
- options: Parameters
-) {
- const { model, view, base } = await getModelViewBase(req);
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
- res.json(await baseModel[operation].apply(null, options));
-}
-
-async function bulkDataInsert(req: Request, res: Response) {
- await executeBulkOperation(req, res, 'bulkInsert', [
- req.body,
- { cookie: req },
- ]);
-}
-
-async function bulkDataUpdate(req: Request, res: Response) {
- await executeBulkOperation(req, res, 'bulkUpdate', [
- req.body,
- { cookie: req },
- ]);
-}
-
-// todo: Integrate with filterArrJson bulkDataUpdateAll
-async function bulkDataUpdateAll(req: Request, res: Response) {
- await executeBulkOperation(req, res, 'bulkUpdateAll', [
- req.query,
- req.body,
- { cookie: req },
- ]);
-}
-
-async function bulkDataDelete(req: Request, res: Response) {
- await executeBulkOperation(req, res, 'bulkDelete', [
- req.body,
- { cookie: req },
- ]);
-}
-
-// todo: Integrate with filterArrJson bulkDataDeleteAll
-async function bulkDataDeleteAll(req: Request, res: Response) {
- await executeBulkOperation(req, res, 'bulkDeleteAll', [req.query]);
-}
-const router = Router({ mergeParams: true });
-
-router.post(
- '/api/v1/db/data/bulk/:orgs/:projectName/:tableName',
- apiMetrics,
- ncMetaAclMw(bulkDataInsert, 'bulkDataInsert')
-);
-router.patch(
- '/api/v1/db/data/bulk/:orgs/:projectName/:tableName',
- apiMetrics,
- ncMetaAclMw(bulkDataUpdate, 'bulkDataUpdate')
-);
-router.patch(
- '/api/v1/db/data/bulk/:orgs/:projectName/:tableName/all',
- apiMetrics,
- ncMetaAclMw(bulkDataUpdateAll, 'bulkDataUpdateAll')
-);
-router.delete(
- '/api/v1/db/data/bulk/:orgs/:projectName/:tableName',
- apiMetrics,
- ncMetaAclMw(bulkDataDelete, 'bulkDataDelete')
-);
-router.delete(
- '/api/v1/db/data/bulk/:orgs/:projectName/:tableName/all',
- apiMetrics,
- ncMetaAclMw(bulkDataDeleteAll, 'bulkDataDeleteAll')
-);
-
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts b/packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts
deleted file mode 100644
index 4ef4b7ce5e..0000000000
--- a/packages/nocodb/src/lib/meta/api/dataApis/dataAliasApis.ts
+++ /dev/null
@@ -1,432 +0,0 @@
-import { Request, Response, Router } from 'express';
-import Model from '../../../models/Model';
-import { nocoExecute } from 'nc-help';
-import Base from '../../../models/Base';
-import NcConnectionMgrv2 from '../../../utils/common/NcConnectionMgrv2';
-import { NcError } from '../../helpers/catchError';
-import { PagedResponseImpl } from '../../helpers/PagedResponse';
-import View from '../../../models/View';
-import ncMetaAclMw from '../../helpers/ncMetaAclMw';
-import { getViewAndModelFromRequestByAliasOrId } from './helpers';
-import apiMetrics from '../../helpers/apiMetrics';
-import getAst from '../../../db/sql-data-mapper/lib/sql/helpers/getAst';
-import { parseHrtimeToSeconds } from '../helpers';
-
-// todo: Handle the error case where view doesnt belong to model
-async function dataList(req: Request, res: Response) {
- const startTime = process.hrtime();
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
- const responseData = await getDataList(model, view, req);
- const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime));
- res.setHeader('xc-db-response', elapsedSeconds);
- res.json(responseData);
-}
-
-async function dataFindOne(req: Request, res: Response) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
- res.json(await getFindOne(model, view, req));
-}
-
-async function dataGroupBy(req: Request, res: Response) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
- res.json(await getDataGroupBy(model, view, req));
-}
-
-async function dataCount(req: Request, res: Response) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- const countArgs: any = { ...req.query };
- try {
- countArgs.filterArr = JSON.parse(countArgs.filterArrJson);
- } catch (e) {}
-
- const count = await baseModel.count(countArgs);
-
- res.json({ count });
-}
-
-// todo: Handle the error case where view doesnt belong to model
-async function dataInsert(req: Request, res: Response) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- res.json(await baseModel.insert(req.body, null, req));
-}
-
-async function dataUpdate(req: Request, res: Response) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- res.json(await baseModel.updateByPk(req.params.rowId, req.body, null, req));
-}
-
-async function dataDelete(req: Request, res: Response) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
- const base = await Base.get(model.base_id);
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- // todo: Should have error http status code
- const message = await baseModel.hasLTARData(req.params.rowId, model);
- if (message.length) {
- res.json({ message });
- return;
- }
- res.json(await baseModel.delByPk(req.params.rowId, null, req));
-}
-
-async function getDataList(model, view: View, req) {
- 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({ model, query: req.query, view });
-
- const listArgs: any = { ...req.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(
- requestObj,
- await baseModel.list(listArgs),
- {},
- listArgs
- );
- count = await baseModel.count(listArgs);
- } catch (e) {
- console.log(e);
- NcError.internalServerError(
- 'Internal Server Error, check server log for more details'
- );
- }
-
- return new PagedResponseImpl(data, {
- ...req.query,
- count,
- });
-}
-
-async function getFindOne(model, view: View, req) {
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- const args: any = { ...req.query };
- try {
- args.filterArr = JSON.parse(args.filterArrJson);
- } catch (e) {}
- try {
- args.sortArr = JSON.parse(args.sortArrJson);
- } catch (e) {}
-
- const data = await baseModel.findOne(args);
- return data
- ? await nocoExecute(
- await getAst({ model, query: args, view }),
- data,
- {},
- {}
- )
- : {};
-}
-
-async function getDataGroupBy(model, view: View, req) {
- 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 = { ...req.query };
- const data = await baseModel.groupBy({ ...req.query });
- const count = await baseModel.count(listArgs);
-
- return new PagedResponseImpl(data, {
- ...req.query,
- count,
- });
-}
-
-async function dataRead(req: Request, res: Response) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- const row = await baseModel.readByPk(req.params.rowId);
-
- if (!row) {
- NcError.notFound();
- }
-
- res.json(
- await nocoExecute(
- await getAst({ model, query: req.query, view }),
- row,
- {},
- req.query
- )
- );
-}
-
-async function dataExist(req: Request, res: Response) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- res.json(await baseModel.exist(req.params.rowId));
-}
-
-// todo: Handle the error case where view doesnt belong to model
-async function groupedDataList(req: Request, res: Response) {
- const startTime = process.hrtime();
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
- const groupedData = await getGroupedDataList(model, view, req);
- const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime));
- res.setHeader('xc-db-response', elapsedSeconds);
- res.json(groupedData);
-}
-
-async function getGroupedDataList(model, view: View, req) {
- 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({ model, query: req.query, view });
-
- const listArgs: any = { ...req.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 = [];
- // let count = 0
- try {
- const groupedData = await baseModel.groupedList({
- ...listArgs,
- groupColumnId: req.params.columnId,
- });
- data = await nocoExecute(
- { key: 1, value: requestObj },
- groupedData,
- {},
- listArgs
- );
- const countArr = await baseModel.groupedListCount({
- ...listArgs,
- groupColumnId: req.params.columnId,
- });
- 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, {
- ...req.query,
- count: count,
- });
- return item;
- });
- } catch (e) {
- console.log(e);
- NcError.internalServerError(
- 'Internal Server Error, check server log for more details'
- );
- }
- return data;
-}
-
-const router = Router({ mergeParams: true });
-
-// table data crud apis
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName',
- apiMetrics,
- ncMetaAclMw(dataList, 'dataList')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/find-one',
- apiMetrics,
- ncMetaAclMw(dataFindOne, 'dataFindOne')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/groupby',
- apiMetrics,
- ncMetaAclMw(dataGroupBy, 'dataGroupBy')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/group/:columnId',
- apiMetrics,
- ncMetaAclMw(groupedDataList, 'groupedDataList')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/exist',
- apiMetrics,
- ncMetaAclMw(dataExist, 'dataExist')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/count',
- apiMetrics,
- ncMetaAclMw(dataCount, 'dataCount')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/count',
- apiMetrics,
- ncMetaAclMw(dataCount, 'dataCount')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId',
- apiMetrics,
- ncMetaAclMw(dataRead, 'dataRead')
-);
-
-router.patch(
- '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId',
- apiMetrics,
- ncMetaAclMw(dataUpdate, 'dataUpdate')
-);
-
-router.delete(
- '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId',
- apiMetrics,
- ncMetaAclMw(dataDelete, 'dataDelete')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName',
- apiMetrics,
- ncMetaAclMw(dataList, 'dataList')
-);
-
-// table view data crud apis
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName',
- apiMetrics,
- ncMetaAclMw(dataList, 'dataList')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/find-one',
- apiMetrics,
- ncMetaAclMw(dataFindOne, 'dataFindOne')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/groupby',
- apiMetrics,
- ncMetaAclMw(dataGroupBy, 'dataGroupBy')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/group/:columnId',
- apiMetrics,
- ncMetaAclMw(groupedDataList, 'groupedDataList')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId/exist',
- apiMetrics,
- ncMetaAclMw(dataExist, 'dataExist')
-);
-
-router.post(
- '/api/v1/db/data/:orgs/:projectName/:tableName',
- apiMetrics,
- ncMetaAclMw(dataInsert, 'dataInsert')
-);
-
-router.post(
- '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName',
- apiMetrics,
- ncMetaAclMw(dataInsert, 'dataInsert')
-);
-
-router.patch(
- '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId',
- apiMetrics,
- ncMetaAclMw(dataUpdate, 'dataUpdate')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId',
- apiMetrics,
- ncMetaAclMw(dataRead, 'dataRead')
-);
-
-router.delete(
- '/api/v1/db/data/:orgs/:projectName/:tableName/views/:viewName/:rowId',
- apiMetrics,
- ncMetaAclMw(dataDelete, 'dataDelete')
-);
-
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/dataApis/dataAliasNestedApis.ts b/packages/nocodb/src/lib/meta/api/dataApis/dataAliasNestedApis.ts
deleted file mode 100644
index d260694fe5..0000000000
--- a/packages/nocodb/src/lib/meta/api/dataApis/dataAliasNestedApis.ts
+++ /dev/null
@@ -1,291 +0,0 @@
-import { Request, Response, Router } from 'express';
-import Model from '../../../models/Model';
-import Base from '../../../models/Base';
-import NcConnectionMgrv2 from '../../../utils/common/NcConnectionMgrv2';
-import { PagedResponseImpl } from '../../helpers/PagedResponse';
-import ncMetaAclMw from '../../helpers/ncMetaAclMw';
-import {
- getColumnByIdOrName,
- getViewAndModelFromRequestByAliasOrId,
-} from './helpers';
-import { NcError } from '../../helpers/catchError';
-import apiMetrics from '../../helpers/apiMetrics';
-
-// todo: handle case where the given column is not ltar
-export async function mmList(req: Request, res: Response, next) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
-
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- const column = await getColumnByIdOrName(req.params.columnName, model);
-
- const data = await baseModel.mmList(
- {
- colId: column.id,
- parentId: req.params.rowId,
- },
- req.query as any
- );
- const count: any = await baseModel.mmListCount({
- colId: column.id,
- parentId: req.params.rowId,
- });
-
- res.json(
- new PagedResponseImpl(data, {
- count,
- ...req.query,
- })
- );
-}
-
-export async function mmExcludedList(req: Request, res: Response, next) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
- const column = await getColumnByIdOrName(req.params.columnName, model);
-
- const data = await baseModel.getMmChildrenExcludedList(
- {
- colId: column.id,
- pid: req.params.rowId,
- },
- req.query
- );
-
- const count = await baseModel.getMmChildrenExcludedListCount(
- {
- colId: column.id,
- pid: req.params.rowId,
- },
- req.query
- );
-
- res.json(
- new PagedResponseImpl(data, {
- count,
- ...req.query,
- })
- );
-}
-
-export async function hmExcludedList(req: Request, res: Response, next) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
-
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- const column = await getColumnByIdOrName(req.params.columnName, model);
-
- const data = await baseModel.getHmChildrenExcludedList(
- {
- colId: column.id,
- pid: req.params.rowId,
- },
- req.query
- );
-
- const count = await baseModel.getHmChildrenExcludedListCount(
- {
- colId: column.id,
- pid: req.params.rowId,
- },
- req.query
- );
-
- res.json(
- new PagedResponseImpl(data, {
- count,
- ...req.query,
- })
- );
-}
-
-export async function btExcludedList(req: Request, res: Response, next) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- const column = await getColumnByIdOrName(req.params.columnName, model);
-
- const data = await baseModel.getBtChildrenExcludedList(
- {
- colId: column.id,
- cid: req.params.rowId,
- },
- req.query
- );
-
- const count = await baseModel.getBtChildrenExcludedListCount(
- {
- colId: column.id,
- cid: req.params.rowId,
- },
- req.query
- );
-
- res.json(
- new PagedResponseImpl(data, {
- count,
- ...req.query,
- })
- );
-}
-
-// todo: handle case where the given column is not ltar
-export async function hmList(req: Request, res: Response, next) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- const column = await getColumnByIdOrName(req.params.columnName, model);
-
- const data = await baseModel.hmList(
- {
- colId: column.id,
- id: req.params.rowId,
- },
- req.query
- );
-
- const count = await baseModel.hmListCount({
- colId: column.id,
- id: req.params.rowId,
- });
-
- res.json(
- new PagedResponseImpl(data, {
- count,
- ...req.query,
- } as any)
- );
-}
-
-//@ts-ignore
-async function relationDataRemove(req, res) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
-
- if (!model) NcError.notFound('Table not found');
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- const column = await getColumnByIdOrName(req.params.columnName, model);
-
- await baseModel.removeChild({
- colId: column.id,
- childId: req.params.refRowId,
- rowId: req.params.rowId,
- cookie: req,
- });
-
- res.json({ msg: 'success' });
-}
-
-//@ts-ignore
-// todo: Give proper error message when reference row is already related and handle duplicate ref row id in hm
-async function relationDataAdd(req, res) {
- const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
- if (!model) NcError.notFound('Table not found');
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- const column = await getColumnByIdOrName(req.params.columnName, model);
- await baseModel.addChild({
- colId: column.id,
- childId: req.params.refRowId,
- rowId: req.params.rowId,
- cookie: req,
- });
-
- res.json({ msg: 'success' });
-}
-
-const router = Router({ mergeParams: true });
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/mm/:columnName/exclude',
- apiMetrics,
- ncMetaAclMw(mmExcludedList, 'mmExcludedList')
-);
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/hm/:columnName/exclude',
- apiMetrics,
- ncMetaAclMw(hmExcludedList, 'hmExcludedList')
-);
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/bt/:columnName/exclude',
- apiMetrics,
- ncMetaAclMw(btExcludedList, 'btExcludedList')
-);
-
-router.post(
- '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/:relationType/:columnName/:refRowId',
- apiMetrics,
- ncMetaAclMw(relationDataAdd, 'relationDataAdd')
-);
-router.delete(
- '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/:relationType/:columnName/:refRowId',
- apiMetrics,
- ncMetaAclMw(relationDataRemove, 'relationDataRemove')
-);
-
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/mm/:columnName',
- apiMetrics,
- ncMetaAclMw(mmList, 'mmList')
-);
-router.get(
- '/api/v1/db/data/:orgs/:projectName/:tableName/:rowId/hm/:columnName',
- apiMetrics,
- ncMetaAclMw(hmList, 'hmList')
-);
-
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/dataApis/dataApis.ts b/packages/nocodb/src/lib/meta/api/dataApis/dataApis.ts
deleted file mode 100644
index b8a8899b5f..0000000000
--- a/packages/nocodb/src/lib/meta/api/dataApis/dataApis.ts
+++ /dev/null
@@ -1,612 +0,0 @@
-import { Request, Response, Router } from 'express';
-import Model from '../../../models/Model';
-import { nocoExecute } from 'nc-help';
-import Base from '../../../models/Base';
-import NcConnectionMgrv2 from '../../../utils/common/NcConnectionMgrv2';
-import { PagedResponseImpl } from '../../helpers/PagedResponse';
-import View from '../../../models/View';
-import ncMetaAclMw from '../../helpers/ncMetaAclMw';
-import { NcError } from '../../helpers/catchError';
-import apiMetrics from '../../helpers/apiMetrics';
-import getAst from '../../../db/sql-data-mapper/lib/sql/helpers/getAst';
-
-export async function dataList(req: Request, res: Response, next) {
- const view = await View.get(req.params.viewId);
-
- const model = await Model.getByIdOrName({
- id: view?.fk_model_id || req.params.viewId,
- });
-
- if (!model) return next(new Error('Table not found'));
-
- res.json(await getDataList(model, view, req));
-}
-
-export async function mmList(req: Request, res: Response, next) {
- const view = await View.get(req.params.viewId);
-
- const model = await Model.getByIdOrName({
- id: view?.fk_model_id || req.params.viewId,
- });
-
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- const key = `${model.title}List`;
- const requestObj: any = {
- [key]: 1,
- };
-
- const data = (
- await nocoExecute(
- requestObj,
- {
- [key]: async (args) => {
- return await baseModel.mmList(
- {
- colId: req.params.colId,
- parentId: req.params.rowId,
- },
- args
- );
- },
- },
- {},
-
- { nested: { [key]: req.query } }
- )
- )?.[key];
-
- const count: any = await baseModel.mmListCount({
- colId: req.params.colId,
- parentId: req.params.rowId,
- });
-
- res.json(
- new PagedResponseImpl(data, {
- count,
- ...req.query,
- })
- );
-}
-
-export async function mmExcludedList(req: Request, res: Response, next) {
- const view = await View.get(req.params.viewId);
-
- const model = await Model.getByIdOrName({
- id: view?.fk_model_id || req.params.viewId,
- });
-
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: 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.getMmChildrenExcludedList(
- {
- colId: req.params.colId,
- pid: req.params.rowId,
- },
- args
- );
- },
- },
- {},
-
- { nested: { [key]: req.query } }
- )
- )?.[key];
-
- const count = await baseModel.getMmChildrenExcludedListCount(
- {
- colId: req.params.colId,
- pid: req.params.rowId,
- },
- req.query
- );
-
- res.json(
- new PagedResponseImpl(data, {
- count,
- ...req.query,
- })
- );
-}
-
-export async function hmExcludedList(req: Request, res: Response, next) {
- const view = await View.get(req.params.viewId);
-
- const model = await Model.getByIdOrName({
- id: view?.fk_model_id || req.params.viewId,
- });
-
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: 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.getHmChildrenExcludedList(
- {
- colId: req.params.colId,
- pid: req.params.rowId,
- },
- args
- );
- },
- },
- {},
-
- { nested: { [key]: req.query } }
- )
- )?.[key];
-
- const count = await baseModel.getHmChildrenExcludedListCount(
- {
- colId: req.params.colId,
- pid: req.params.rowId,
- },
- req.query
- );
-
- res.json(
- new PagedResponseImpl(data, {
- count,
- ...req.query,
- })
- );
-}
-
-export async function btExcludedList(req: Request, res: Response, next) {
- const view = await View.get(req.params.viewId);
-
- const model = await Model.getByIdOrName({
- id: view?.fk_model_id || req.params.viewId,
- });
-
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: 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.getBtChildrenExcludedList(
- {
- colId: req.params.colId,
- cid: req.params.rowId,
- },
- args
- );
- },
- },
- {},
-
- { nested: { [key]: req.query } }
- )
- )?.[key];
-
- const count = await baseModel.getBtChildrenExcludedListCount(
- {
- colId: req.params.colId,
- cid: req.params.rowId,
- },
- req.query
- );
-
- res.json(
- new PagedResponseImpl(data, {
- count,
- ...req.query,
- })
- );
-}
-
-export async function hmList(req: Request, res: Response, next) {
- const view = await View.get(req.params.viewId);
-
- const model = await Model.getByIdOrName({
- id: view?.fk_model_id || req.params.viewId,
- });
-
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- const key = `${model.title}List`;
- const requestObj: any = {
- [key]: 1,
- };
-
- const data = (
- await nocoExecute(
- requestObj,
- {
- [key]: async (args) => {
- return await baseModel.hmList(
- {
- colId: req.params.colId,
- id: req.params.rowId,
- },
- args
- );
- },
- },
- {},
- { nested: { [key]: req.query } }
- )
- )?.[key];
-
- const count = await baseModel.hmListCount({
- colId: req.params.colId,
- id: req.params.rowId,
- });
-
- res.json(
- new PagedResponseImpl(data, {
- totalRows: count,
- } as any)
- );
-}
-
-async function dataRead(req: Request, res: Response, next) {
- try {
- const model = await Model.getByIdOrName({
- id: req.params.viewId,
- });
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- res.json(
- await nocoExecute(
- await getAst({ model, query: req.query }),
- await baseModel.readByPk(req.params.rowId),
- {},
- {}
- )
- );
- } catch (e) {
- console.log(e);
- NcError.internalServerError(
- 'Internal Server Error, check server log for more details'
- );
- }
-}
-
-async function dataInsert(req: Request, res: Response, next) {
- try {
- const model = await Model.getByIdOrName({
- id: req.params.viewId,
- });
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- res.json(await baseModel.insert(req.body, null, req));
- } catch (e) {
- console.log(e);
- res.status(500).json({ msg: e.message });
- }
-}
-
-// async function dataInsertNew(req: Request, res: Response) {
-// const { model, view } = await getViewAndModelFromRequest(req);
-//
-// const base = await Base.get(model.base_id);
-//
-// const baseModel = await Model.getBaseModelSQL({
-// id: model.id,
-// viewId: view?.id,
-// dbDriver: NcConnectionMgrv2.get(base)
-// });
-//
-// res.json(await baseModel.insert(req.body, null, req));
-// }
-
-// async function dataUpdateNew(req: Request, res: Response) {
-// const { model, view } = await getViewAndModelFromRequest(req);
-// const base = await Base.get(model.base_id);
-//
-// const baseModel = await Model.getBaseModelSQL({
-// id: model.id,
-// viewId: view.id,
-// dbDriver: NcConnectionMgrv2.get(base)
-// });
-//
-// res.json(await baseModel.updateByPk(req.params.rowId, req.body, null, req));
-// }
-async function dataUpdate(req: Request, res: Response, next) {
- try {
- const model = await Model.getByIdOrName({
- id: req.params.viewId,
- });
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- res.json(await baseModel.updateByPk(req.params.rowId, req.body, null, req));
- } catch (e) {
- console.log(e);
- res.status(500).json({ msg: e.message });
- }
-}
-//
-// async function dataDeleteNew(req: Request, res: Response) {
-// const { model, view } = await getViewAndModelFromRequest(req);
-// const base = await Base.get(model.base_id);
-// const baseModel = await Model.getBaseModelSQL({
-// id: model.id,
-// viewId: view.id,
-// dbDriver: NcConnectionMgrv2.get(base)
-// });
-//
-// res.json(await baseModel.delByPk(req.params.rowId, null, req));
-// }
-
-async function dataDelete(req: Request, res: Response, next) {
- try {
- const model = await Model.getByIdOrName({
- id: req.params.viewId,
- });
- if (!model) return next(new Error('Table not found'));
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- res.json(await baseModel.delByPk(req.params.rowId, null, req));
- } catch (e) {
- console.log(e);
- res.status(500).json({ msg: e.message });
- }
-}
-
-async function getDataList(model, view: View, req) {
- 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 });
-
- const listArgs: any = { ...req.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(
- requestObj,
- 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
- console.log(e);
- NcError.internalServerError(
- 'Internal Server Error, check server log for more details'
- );
- }
-
- return new PagedResponseImpl(data, {
- count,
- ...req.query,
- });
-}
-//@ts-ignore
-async function relationDataDelete(req, res) {
- const view = await View.get(req.params.viewId);
-
- const model = await Model.getByIdOrName({
- id: view?.fk_model_id || req.params.viewId,
- });
-
- if (!model) NcError.notFound('Table not found');
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- await baseModel.removeChild({
- colId: req.params.colId,
- childId: req.params.childId,
- rowId: req.params.rowId,
- cookie: req,
- });
-
- res.json({ msg: 'success' });
-}
-
-//@ts-ignore
-async function relationDataAdd(req, res) {
- const view = await View.get(req.params.viewId);
-
- const model = await Model.getByIdOrName({
- id: view?.fk_model_id || req.params.viewId,
- });
-
- if (!model) NcError.notFound('Table not found');
-
- const base = await Base.get(model.base_id);
-
- const baseModel = await Model.getBaseModelSQL({
- id: model.id,
- viewId: view?.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- await baseModel.addChild({
- colId: req.params.colId,
- childId: req.params.childId,
- rowId: req.params.rowId,
- cookie: req,
- });
-
- res.json({ msg: 'success' });
-}
-
-const router = Router({ mergeParams: true });
-
-// router.get('/data/:orgs/:projectName/:tableName',apiMetrics,ncMetaAclMw(dataListNew));
-// router.get(
-// '/data/:orgs/:projectName/:tableName/views/:viewName',
-// ncMetaAclMw(dataListNew)
-// );
-//
-// router.post(
-// '/data/:orgs/:projectName/:tableName/views/:viewName',
-// ncMetaAclMw(dataInsertNew)
-// );
-// router.patch(
-// '/data/:orgs/:projectName/:tableName/views/:viewName/:rowId',
-// ncMetaAclMw(dataUpdateNew)
-// );
-// router.delete(
-// '/data/:orgs/:projectName/:tableName/views/:viewName/:rowId',
-// ncMetaAclMw(dataDeleteNew)
-// );
-
-router.get('/data/:viewId/', apiMetrics, ncMetaAclMw(dataList, 'dataList'));
-router.post(
- '/data/:viewId/',
- apiMetrics,
- ncMetaAclMw(dataInsert, 'dataInsert')
-);
-router.get(
- '/data/:viewId/:rowId',
- apiMetrics,
- ncMetaAclMw(dataRead, 'dataRead')
-);
-router.patch(
- '/data/:viewId/:rowId',
- apiMetrics,
- ncMetaAclMw(dataUpdate, 'dataUpdate')
-);
-router.delete(
- '/data/:viewId/:rowId',
- apiMetrics,
- ncMetaAclMw(dataDelete, 'dataDelete')
-);
-
-router.get(
- '/data/:viewId/:rowId/mm/:colId',
- apiMetrics,
- ncMetaAclMw(mmList, 'mmList')
-);
-router.get(
- '/data/:viewId/:rowId/hm/:colId',
- apiMetrics,
- ncMetaAclMw(hmList, 'hmList')
-);
-
-router.get(
- '/data/:viewId/:rowId/mm/:colId/exclude',
- ncMetaAclMw(mmExcludedList, 'mmExcludedList')
-);
-router.get(
- '/data/:viewId/:rowId/hm/:colId/exclude',
- ncMetaAclMw(hmExcludedList, 'hmExcludedList')
-);
-router.get(
- '/data/:viewId/:rowId/bt/:colId/exclude',
- ncMetaAclMw(btExcludedList, 'btExcludedList')
-);
-
-router.post(
- '/data/:viewId/:rowId/:relationType/:colId/:childId',
- ncMetaAclMw(relationDataAdd, 'relationDataAdd')
-);
-router.delete(
- '/data/:viewId/:rowId/:relationType/:colId/:childId',
- ncMetaAclMw(relationDataDelete, 'relationDataDelete')
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/dataApis/index.ts b/packages/nocodb/src/lib/meta/api/dataApis/index.ts
deleted file mode 100644
index 34bb194ee0..0000000000
--- a/packages/nocodb/src/lib/meta/api/dataApis/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import dataApis from './dataApis';
-import oldDataApis from './oldDataApis';
-import dataAliasApis from './dataAliasApis';
-import bulkDataAliasApis from './bulkDataAliasApis';
-import dataAliasNestedApis from './dataAliasNestedApis';
-import dataAliasExportApis from './dataAliasExportApis';
-
-export {
- dataApis,
- oldDataApis,
- dataAliasApis,
- bulkDataAliasApis,
- dataAliasNestedApis,
- dataAliasExportApis,
-};
diff --git a/packages/nocodb/src/lib/meta/api/ee/orgTokenApis.ts b/packages/nocodb/src/lib/meta/api/ee/orgTokenApis.ts
deleted file mode 100644
index 410d040c11..0000000000
--- a/packages/nocodb/src/lib/meta/api/ee/orgTokenApis.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { OrgUserRoles } from 'nocodb-sdk';
-import ApiToken from '../../../models/ApiToken';
-import { PagedResponseImpl } from '../../helpers/PagedResponse';
-
-export async function apiTokenListEE(req, res) {
- let fk_user_id = req.user.id;
-
- // if super admin get all tokens
- if (req.user.roles.includes(OrgUserRoles.SUPER_ADMIN)) {
- fk_user_id = undefined;
- }
-
- res.json(
- new PagedResponseImpl(
- await ApiToken.listWithCreatedBy({ ...req.query, fk_user_id }),
- {
- ...req.query,
- count: await ApiToken.count({}),
- }
- )
- );
-}
diff --git a/packages/nocodb/src/lib/meta/api/filterApis.ts b/packages/nocodb/src/lib/meta/api/filterApis.ts
deleted file mode 100644
index 1be850b7d8..0000000000
--- a/packages/nocodb/src/lib/meta/api/filterApis.ts
+++ /dev/null
@@ -1,175 +0,0 @@
-import { Request, Response, Router } from 'express';
-// @ts-ignore
-import Model from '../../models/Model';
-import { Tele } from 'nc-help';
-// @ts-ignore
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-// @ts-ignore
-import { Table, TableList, TableListParams, TableReq } from 'nocodb-sdk';
-// @ts-ignore
-import ProjectMgrv2 from '../../db/sql-mgr/v2/ProjectMgrv2';
-// @ts-ignore
-import Project from '../../models/Project';
-import Filter from '../../models/Filter';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
-
-// @ts-ignore
-export async function filterGet(req: Request, res: Response, next) {
- try {
- const filter = await Filter.get(req.params.filterId);
-
- res.json(filter);
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-
-// @ts-ignore
-export async function filterList(
- req: Request,
- res: Response,
- next
-) {
- try {
- const filter = await Filter.rootFilterList({ viewId: req.params.viewId });
-
- res.json(filter);
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-// @ts-ignore
-export async function filterChildrenRead(
- req: Request,
- res: Response,
- next
-) {
- try {
- const filter = await Filter.parentFilterList({
- parentId: req.params.filterParentId,
- });
-
- res.json(filter);
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-
-export async function filterCreate(
- req: Request,
- res,
- next
-) {
- try {
- const filter = await Filter.insert({
- ...req.body,
- fk_view_id: req.params.viewId,
- });
-
- Tele.emit('evt', { evt_type: 'filter:created' });
- res.json(filter);
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-
-// @ts-ignore
-export async function filterUpdate(req, res, next) {
- try {
- const filter = await Filter.update(req.params.filterId, {
- ...req.body,
- fk_view_id: req.params.viewId,
- });
- Tele.emit('evt', { evt_type: 'filter:updated' });
- res.json(filter);
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-
-// @ts-ignore
-export async function filterDelete(req: Request, res: Response, next) {
- try {
- const filter = await Filter.delete(req.params.filterId);
- Tele.emit('evt', { evt_type: 'filter:deleted' });
- res.json(filter);
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-
-export async function hookFilterList(
- req: Request,
- res: Response
-) {
- const filter = await Filter.rootFilterListByHook({
- hookId: req.params.hookId,
- });
-
- res.json(filter);
-}
-
-export async function hookFilterCreate(req: Request, res) {
- const filter = await Filter.insert({
- ...req.body,
- fk_hook_id: req.params.hookId,
- });
-
- Tele.emit('evt', { evt_type: 'hookFilter:created' });
- res.json(filter);
-}
-
-const router = Router({ mergeParams: true });
-router.get(
- '/api/v1/db/meta/views/:viewId/filters',
- metaApiMetrics,
- ncMetaAclMw(filterList, 'filterList')
-);
-router.post(
- '/api/v1/db/meta/views/:viewId/filters',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
- ncMetaAclMw(filterCreate, 'filterCreate')
-);
-
-router.get(
- '/api/v1/db/meta/hooks/:hookId/filters',
- ncMetaAclMw(hookFilterList, 'filterList')
-);
-router.post(
- '/api/v1/db/meta/hooks/:hookId/filters',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
- ncMetaAclMw(hookFilterCreate, 'filterCreate')
-);
-
-router.get(
- '/api/v1/db/meta/filters/:filterId',
- metaApiMetrics,
- ncMetaAclMw(filterGet, 'filterGet')
-);
-router.patch(
- '/api/v1/db/meta/filters/:filterId',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
- ncMetaAclMw(filterUpdate, 'filterUpdate')
-);
-router.delete(
- '/api/v1/db/meta/filters/:filterId',
- metaApiMetrics,
- ncMetaAclMw(filterDelete, 'filterDelete')
-);
-router.get(
- '/api/v1/db/meta/filters/:filterParentId/children',
- metaApiMetrics,
- ncMetaAclMw(filterChildrenRead, 'filterChildrenRead')
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/formViewApis.ts b/packages/nocodb/src/lib/meta/api/formViewApis.ts
deleted file mode 100644
index beadf3cded..0000000000
--- a/packages/nocodb/src/lib/meta/api/formViewApis.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { Request, Response, Router } from 'express';
-// @ts-ignore
-import Model from '../../models/Model';
-import { Tele } from 'nc-help';
-// @ts-ignore
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-import { FormType, ViewTypes } from 'nocodb-sdk';
-// @ts-ignore
-import ProjectMgrv2 from '../../db/sql-mgr/v2/ProjectMgrv2';
-// @ts-ignore
-import Project from '../../models/Project';
-import View from '../../models/View';
-import FormView from '../../models/FormView';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
-
-// @ts-ignore
-export async function formViewGet(req: Request, res: Response) {
- const formViewData = await FormView.getWithInfo(req.params.formViewId);
- res.json(formViewData);
-}
-
-export async function formViewCreate(req: Request, res) {
- Tele.emit('evt', { evt_type: 'vtable:created', show_as: 'form' });
- const view = await View.insert({
- ...req.body,
- // todo: sanitize
- fk_model_id: req.params.tableId,
- type: ViewTypes.FORM,
- });
- res.json(view);
-}
-// @ts-ignore
-export async function formViewUpdate(req, res) {
- Tele.emit('evt', { evt_type: 'view:updated', type: 'grid' });
- res.json(await FormView.update(req.params.formViewId, req.body));
-}
-
-// @ts-ignore
-export async function formViewDelete(req: Request, res: Response, next) {}
-
-const router = Router({ mergeParams: true });
-router.post(
- '/api/v1/db/meta/tables/:tableId/forms',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/FormCreateReq'),
- ncMetaAclMw(formViewCreate, 'formViewCreate')
-);
-router.get(
- '/api/v1/db/meta/forms/:formViewId',
- metaApiMetrics,
- ncMetaAclMw(formViewGet, 'formViewGet')
-);
-router.patch(
- '/api/v1/db/meta/forms/:formViewId',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/FormReq'),
- ncMetaAclMw(formViewUpdate, 'formViewUpdate')
-);
-router.delete(
- '/api/v1/db/meta/forms/:formViewId',
- metaApiMetrics,
- ncMetaAclMw(formViewDelete, 'formViewDelete')
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/formViewColumnApis.ts b/packages/nocodb/src/lib/meta/api/formViewColumnApis.ts
deleted file mode 100644
index bad2f91b0a..0000000000
--- a/packages/nocodb/src/lib/meta/api/formViewColumnApis.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Request, Response, Router } from 'express';
-import FormViewColumn from '../../models/FormViewColumn';
-import { Tele } from 'nc-help';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
-
-export async function columnUpdate(req: Request, res: Response) {
- Tele.emit('evt', { evt_type: 'formViewColumn:updated' });
- res.json(await FormViewColumn.update(req.params.formViewColumnId, req.body));
-}
-
-const router = Router({ mergeParams: true });
-router.patch(
- '/api/v1/db/meta/form-columns/:formViewColumnId',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/FormColumnReq'),
- ncMetaAclMw(columnUpdate, 'columnUpdate')
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/galleryViewApis.ts b/packages/nocodb/src/lib/meta/api/galleryViewApis.ts
deleted file mode 100644
index 2c6c79db9e..0000000000
--- a/packages/nocodb/src/lib/meta/api/galleryViewApis.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { Request, Response, Router } from 'express';
-import { GalleryType, ViewTypes } from 'nocodb-sdk';
-import View from '../../models/View';
-import GalleryView from '../../models/GalleryView';
-import { Tele } from 'nc-help';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
-export async function galleryViewGet(req: Request, res: Response) {
- res.json(await GalleryView.get(req.params.galleryViewId));
-}
-
-export async function galleryViewCreate(req: Request, res) {
- Tele.emit('evt', { evt_type: 'vtable:created', show_as: 'gallery' });
- const view = await View.insert({
- ...req.body,
- // todo: sanitize
- fk_model_id: req.params.tableId,
- type: ViewTypes.GALLERY,
- });
- res.json(view);
-}
-
-export async function galleryViewUpdate(req, res) {
- Tele.emit('evt', { evt_type: 'view:updated', type: 'gallery' });
- res.json(await GalleryView.update(req.params.galleryViewId, req.body));
-}
-
-const router = Router({ mergeParams: true });
-router.post(
- '/api/v1/db/meta/tables/:tableId/galleries',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/GalleryReq'),
- ncMetaAclMw(galleryViewCreate, 'galleryViewCreate')
-);
-router.patch(
- '/api/v1/db/meta/galleries/:galleryViewId',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/GalleryReq'),
- ncMetaAclMw(galleryViewUpdate, 'galleryViewUpdate')
-);
-router.get(
- '/api/v1/db/meta/galleries/:galleryViewId',
- metaApiMetrics,
- ncMetaAclMw(galleryViewGet, 'galleryViewGet')
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/gridViewApis.ts b/packages/nocodb/src/lib/meta/api/gridViewApis.ts
deleted file mode 100644
index 96880398cb..0000000000
--- a/packages/nocodb/src/lib/meta/api/gridViewApis.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { Request, Router } from 'express';
-// @ts-ignore
-import Model from '../../models/Model';
-import { Tele } from 'nc-help';
-// @ts-ignore
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-import { ViewTypes } from 'nocodb-sdk';
-// @ts-ignore
-import ProjectMgrv2 from '../../db/sql-mgr/v2/ProjectMgrv2';
-// @ts-ignore
-import Project from '../../models/Project';
-import View from '../../models/View';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import GridView from '../../models/GridView';
-import { getAjvValidatorMw } from './helpers';
-
-// @ts-ignore
-export async function gridViewCreate(req: Request, res) {
- const view = await View.insert({
- ...req.body,
- // todo: sanitize
- fk_model_id: req.params.tableId,
- type: ViewTypes.GRID,
- });
- Tele.emit('evt', { evt_type: 'vtable:created', show_as: 'grid' });
- res.json(view);
-}
-
-export async function gridViewUpdate(req, res) {
- Tele.emit('evt', { evt_type: 'view:updated', type: 'grid' });
- res.json(await GridView.update(req.params.viewId, req.body));
-}
-
-const router = Router({ mergeParams: true });
-router.post(
- '/api/v1/db/meta/tables/:tableId/grids/',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/GridReq'),
- ncMetaAclMw(gridViewCreate, 'gridViewCreate')
-);
-router.patch(
- '/api/v1/db/meta/grids/:viewId',
- metaApiMetrics,
- ncMetaAclMw(gridViewUpdate, 'gridViewUpdate')
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/helpers/apiHelpers.ts b/packages/nocodb/src/lib/meta/api/helpers/apiHelpers.ts
index 08512fb1f3..ec3fdfcf9b 100644
--- a/packages/nocodb/src/lib/meta/api/helpers/apiHelpers.ts
+++ b/packages/nocodb/src/lib/meta/api/helpers/apiHelpers.ts
@@ -3,6 +3,7 @@ import Ajv, { ErrorObject } from 'ajv';
import addFormats from 'ajv-formats';
// @ts-ignore
import swagger from '../../../../schema/swagger.json';
+import { NcError } from '../../helpers/catchError';
export function parseHrtimeToSeconds(hrtime) {
const seconds = (hrtime[0] + hrtime[1] / 1e6).toFixed(3);
@@ -31,10 +32,29 @@ export const getAjvValidatorMw = (schema) => {
// If the request body is invalid, send a response with an error message
res.status(400).json({
- status: 'error',
message: 'Invalid request body',
errors,
});
}
};
};
+
+// a function to validate the payload against the schema
+export const validatePayload = (schema, payload) => {
+ // Validate the request body against the schema
+ const valid = ajv.validate(
+ typeof schema === 'string' ? { $ref: schema } : schema,
+ payload
+ );
+
+ // If the request body is not valid, throw error
+ if (!valid) {
+ const errors: ErrorObject[] | null | undefined = ajv.errors;
+
+ // If the request body is invalid, throw error with error message and errors
+ NcError.ajvValidationError({
+ message: 'Invalid request body',
+ errors,
+ });
+ }
+};
diff --git a/packages/nocodb/src/lib/meta/api/helpers/columnHelpers.ts b/packages/nocodb/src/lib/meta/api/helpers/columnHelpers.ts
index b8a9d57b2c..6c73c3dec9 100644
--- a/packages/nocodb/src/lib/meta/api/helpers/columnHelpers.ts
+++ b/packages/nocodb/src/lib/meta/api/helpers/columnHelpers.ts
@@ -1,9 +1,9 @@
import { customAlphabet } from 'nanoid';
import {
+ BoolType,
ColumnReqType,
LinkToAnotherRecordType,
LookupColumnReqType,
- BoolType,
RelationTypes,
RollupColumnReqType,
TableType,
@@ -77,7 +77,7 @@ export async function createHmAndBtColumn(
}
}
-export async function validateRollupPayload(payload: ColumnReqType) {
+export async function validateRollupPayload(payload: ColumnReqType | Column) {
validateParams(
[
'title',
diff --git a/packages/nocodb/src/lib/meta/api/helpers/populateMeta.ts b/packages/nocodb/src/lib/meta/api/helpers/populateMeta.ts
index ffe246a131..1f71c96bde 100644
--- a/packages/nocodb/src/lib/meta/api/helpers/populateMeta.ts
+++ b/packages/nocodb/src/lib/meta/api/helpers/populateMeta.ts
@@ -11,7 +11,7 @@ import getTableNameAlias, {
import LinkToAnotherRecordColumn from '../../../models/LinkToAnotherRecordColumn';
import getColumnUiType from '../../helpers/getColumnUiType';
import mapDefaultDisplayValue from '../../helpers/mapDefaultDisplayValue';
-import { extractAndGenerateManyToManyRelations } from '../metaDiffApis';
+import { extractAndGenerateManyToManyRelations } from '../../../services/metaDiffService';
import { ModelTypes, UITypes, ViewTypes } from 'nocodb-sdk';
import { IGNORE_TABLES } from '../../../utils/common/BaseApiBuilder';
diff --git a/packages/nocodb/src/lib/meta/api/hookApis.ts b/packages/nocodb/src/lib/meta/api/hookApis.ts
deleted file mode 100644
index 403b969aec..0000000000
--- a/packages/nocodb/src/lib/meta/api/hookApis.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import { Tele } from 'nc-help';
-import catchError from '../helpers/catchError';
-import { Request, Response, Router } from 'express';
-import Hook from '../../models/Hook';
-import { HookListType, HookType } from 'nocodb-sdk';
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-import { invokeWebhook } from '../helpers/webhookHelpers';
-import Model from '../../models/Model';
-import populateSamplePayload from '../helpers/populateSamplePayload';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
-
-export async function hookList(
- req: Request,
- res: Response
-) {
- res.json(
- new PagedResponseImpl(await Hook.list({ fk_model_id: req.params.tableId }))
- );
-}
-
-export async function hookCreate(
- req: Request,
- res: Response
-) {
- Tele.emit('evt', { evt_type: 'webhooks:created' });
- const hook = await Hook.insert({
- ...req.body,
- fk_model_id: req.params.tableId,
- });
- res.json(hook);
-}
-
-export async function hookDelete(
- req: Request,
- res: Response
-) {
- Tele.emit('evt', { evt_type: 'webhooks:deleted' });
- res.json(await Hook.delete(req.params.hookId));
-}
-
-export async function hookUpdate(
- req: Request,
- res: Response
-) {
- Tele.emit('evt', { evt_type: 'webhooks:updated' });
-
- res.json(await Hook.update(req.params.hookId, req.body));
-}
-
-export async function hookTest(req: Request, res: Response) {
- const model = await Model.getByIdOrName({ id: req.params.tableId });
-
- const {
- hook,
- payload: { data, user },
- } = req.body;
- await invokeWebhook(
- new Hook(hook),
- model,
- data,
- user,
- (hook as any)?.filters,
- true
- );
-
- Tele.emit('evt', { evt_type: 'webhooks:tested' });
-
- res.json({ msg: 'Success' });
-}
-export async function tableSampleData(req: Request, res: Response) {
- const model = await Model.getByIdOrName({ id: req.params.tableId });
-
- res // todo: pagination
- .json(await populateSamplePayload(model, false, req.params.operation));
-}
-
-const router = Router({ mergeParams: true });
-router.get(
- '/api/v1/db/meta/tables/:tableId/hooks',
- metaApiMetrics,
- ncMetaAclMw(hookList, 'hookList')
-);
-router.post(
- '/api/v1/db/meta/tables/:tableId/hooks/test',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/HookTestReq'),
- ncMetaAclMw(hookTest, 'hookTest')
-);
-router.post(
- '/api/v1/db/meta/tables/:tableId/hooks',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/HookReq'),
- ncMetaAclMw(hookCreate, 'hookCreate')
-);
-router.delete(
- '/api/v1/db/meta/hooks/:hookId',
- metaApiMetrics,
- ncMetaAclMw(hookDelete, 'hookDelete')
-);
-router.patch(
- '/api/v1/db/meta/hooks/:hookId',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/HookReq'),
- ncMetaAclMw(hookUpdate, 'hookUpdate')
-);
-router.get(
- '/api/v1/db/meta/tables/:tableId/hooks/samplePayload/:operation',
- metaApiMetrics,
- catchError(tableSampleData)
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/hookFilterApis.ts b/packages/nocodb/src/lib/meta/api/hookFilterApis.ts
deleted file mode 100644
index 7b182fcab4..0000000000
--- a/packages/nocodb/src/lib/meta/api/hookFilterApis.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-import { Request, Response, Router } from 'express';
-// @ts-ignore
-import Model from '../../models/Model';
-import { Tele } from 'nc-help';
-// @ts-ignore
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-// @ts-ignore
-import { Table, TableList, TableListParams, TableReq } from 'nocodb-sdk';
-// @ts-ignore
-import ProjectMgrv2 from '../../db/sql-mgr/v2/ProjectMgrv2';
-// @ts-ignore
-import Project from '../../models/Project';
-import Filter from '../../models/Filter';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
-
-// @ts-ignore
-export async function filterGet(req: Request, res: Response, next) {
- try {
- const filter = await Filter.getFilterObject({ hookId: req.params.hookId });
-
- res.json(filter);
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-
-// @ts-ignore
-export async function filterList(
- req: Request,
- res: Response,
- next
-) {
- try {
- const filter = await Filter.rootFilterListByHook({
- hookId: req.params.hookId,
- });
-
- res.json(filter);
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-// @ts-ignore
-export async function filterChildrenRead(
- req: Request,
- res: Response,
- next
-) {
- try {
- const filter = await Filter.parentFilterListByHook({
- hookId: req.params.hookId,
- parentId: req.params.filterParentId,
- });
-
- res.json(filter);
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-
-export async function filterCreate(
- req: Request,
- res,
- next
-) {
- try {
- const filter = await Filter.insert({
- ...req.body,
- fk_hook_id: req.params.hookId,
- });
-
- Tele.emit('evt', { evt_type: 'hookFilter:created' });
- res.json(filter);
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-
-// @ts-ignore
-export async function filterUpdate(req, res, next) {
- try {
- const filter = await Filter.update(req.params.filterId, {
- ...req.body,
- fk_hook_id: req.params.hookId,
- });
- Tele.emit('evt', { evt_type: 'hookFilter:updated' });
- res.json(filter);
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-
-// @ts-ignore
-export async function filterDelete(req: Request, res: Response, next) {
- try {
- const filter = await Filter.delete(req.params.filterId);
- Tele.emit('evt', { evt_type: 'hookFilter:deleted' });
- res.json(filter);
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-
-const router = Router({ mergeParams: true });
-router.get(
- '/hooks/:hookId/filters/',
- metaApiMetrics,
- ncMetaAclMw(filterList, 'filterList')
-);
-router.post(
- '/hooks/:hookId/filters/',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
- ncMetaAclMw(filterCreate, 'filterCreate')
-);
-router.get(
- '/hooks/:hookId/filters/:filterId',
- metaApiMetrics,
- ncMetaAclMw(filterGet, 'filterGet')
-);
-router.patch(
- '/hooks/:hookId/filters/:filterId',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/FilterReq'),
- ncMetaAclMw(filterUpdate, 'filterUpdate')
-);
-router.delete(
- '/hooks/:hookId/filters/:filterId',
- metaApiMetrics,
- ncMetaAclMw(filterDelete, 'filterDelete')
-);
-router.get(
- '/hooks/:hookId/filters/:filterParentId/children',
- metaApiMetrics,
- ncMetaAclMw(filterChildrenRead, 'filterChildrenRead')
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/index.ts b/packages/nocodb/src/lib/meta/api/index.ts
index 67e6203407..a785e5ad77 100644
--- a/packages/nocodb/src/lib/meta/api/index.ts
+++ b/packages/nocodb/src/lib/meta/api/index.ts
@@ -1,114 +1,114 @@
-import { Tele } from 'nc-help';
-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 { T } from 'nc-help';
+import orgLicenseController from '../../controllers/orgLicenseController';
+import orgTokenController from '../../controllers/orgTokenController';
+import orgUserController from '../../controllers/orgUserController';
+import projectController from '../../controllers/projectController';
+import baseController from '../../controllers/baseController';
+import tableController from '../../controllers/tableController';
+import columnController from '../../controllers/columnController';
import { Router } from 'express';
-import sortApis from './sortApis';
-import filterApis from './filterApis';
-import viewColumnApis from './viewColumnApis';
-import gridViewApis from './gridViewApis';
-import viewApis from './viewApis';
-import galleryViewApis from './galleryViewApis';
-import formViewApis from './formViewApis';
-import formViewColumnApis from './formViewColumnApis';
-import attachmentApis from './attachmentApis';
-import exportApis from './exportApis';
-import auditApis from './auditApis';
-import hookApis from './hookApis';
-import pluginApis from './pluginApis';
-import gridViewColumnApis from './gridViewColumnApis';
-import kanbanViewApis from './kanbanViewApis';
-import { userApis } from './userApi';
+import sortController from '../../controllers/sortController';
+import filterController from '../../controllers/filterController';
+import viewColumnController from '../../controllers/viewColumnController';
+import gridViewController from '../../controllers/gridViewController';
+import viewController from '../../controllers/viewController';
+import galleryViewController from '../../controllers/galleryViewController';
+import formViewController from '../../controllers/formViewController';
+import formViewColumnController from '../../controllers/formViewColumnController';
+import attachmentController from '../../controllers/attachmentController';
+import exportController from '../../controllers/exportController';
+import auditController from '../../controllers/auditController';
+import hookController from '../../controllers/hookController';
+import pluginController from '../../controllers/pluginController';
+import gridViewColumnController from '../../controllers/gridViewColumnController';
+import kanbanViewController from '../../controllers/kanbanViewController';
+import { userController } from '../../controllers/userController';
// import extractProjectIdAndAuthenticate from './helpers/extractProjectIdAndAuthenticate';
-import utilApis from './utilApis';
-import projectUserApis from './projectUserApis';
-import sharedBaseApis from './sharedBaseApis';
-import { initStrategies } from './userApi/initStrategies';
-import modelVisibilityApis from './modelVisibilityApis';
-import metaDiffApis from './metaDiffApis';
-import cacheApis from './cacheApis';
-import apiTokenApis from './apiTokenApis';
-import hookFilterApis from './hookFilterApis';
-import testApis from './testApis';
+import utilController from '../../controllers/utilController';
+import projectUserController from '../../controllers/projectUserController';
+import sharedBaseController from '../../controllers/sharedBaseController';
+import { initStrategies } from '../../controllers/userController/initStrategies';
+import modelVisibilityController from '../../controllers/modelVisibilityController';
+import metaDiffController from '../../controllers/metaDiffController';
+import cacheController from '../../controllers/cacheController';
+import apiTokenController from '../../controllers/apiTokenController';
+import hookFilterController from '../../controllers/hookFilterController';
+import testController from '../../controllers/testController';
import {
- bulkDataAliasApis,
- dataAliasApis,
- dataAliasExportApis,
- dataAliasNestedApis,
- dataApis,
- oldDataApis,
-} from './dataApis';
+ bulkDataAliasController,
+ dataAliasController,
+ dataAliasExportController,
+ dataAliasNestedController,
+ dataController,
+ oldDataController,
+} from '../../controllers/dataControllers';
import {
- publicDataApis,
- publicDataExportApis,
- publicMetaApis,
-} from './publicApis';
+ publicDataController,
+ publicDataExportController,
+ publicMetaController,
+} from '../../controllers/publicControllers';
import { Server, Socket } from 'socket.io';
import passport from 'passport';
import crypto from 'crypto';
-import swaggerApis from './swagger/swaggerApis';
-import importApis from './sync/importApis';
-import syncSourceApis from './sync/syncSourceApis';
-import mapViewApis from './mapViewApis';
+import swaggerController from '../../controllers/swaggerController';
+import importController from '../../controllers/syncController/importApis';
+import syncSourceController from '../../controllers/syncController';
+import mapViewController from '../../controllers/mapViewController';
const clients: { [id: string]: Socket } = {};
const jobs: { [id: string]: { last_message: any } } = {};
export default function (router: Router, server) {
initStrategies(router);
- projectApis(router);
- baseApis(router);
- utilApis(router);
+ projectController(router);
+ baseController(router);
+ utilController(router);
if (process.env['PLAYWRIGHT_TEST'] === 'true') {
- router.use(testApis);
+ router.use(testController);
}
- router.use(columnApis);
- router.use(exportApis);
- router.use(dataApis);
- router.use(bulkDataAliasApis);
- router.use(dataAliasApis);
- router.use(dataAliasNestedApis);
- router.use(dataAliasExportApis);
- router.use(oldDataApis);
- router.use(sortApis);
- router.use(filterApis);
- router.use(viewColumnApis);
- router.use(gridViewApis);
- router.use(formViewColumnApis);
- router.use(publicDataApis);
- router.use(publicDataExportApis);
- router.use(publicMetaApis);
- router.use(gridViewColumnApis);
- router.use(tableApis);
- router.use(galleryViewApis);
- router.use(formViewApis);
- router.use(viewApis);
- router.use(attachmentApis);
- router.use(auditApis);
- router.use(hookApis);
- router.use(pluginApis);
- router.use(projectUserApis);
- router.use(orgUserApis);
- router.use(orgTokenApis);
- router.use(orgLicenseApis);
- router.use(sharedBaseApis);
- router.use(modelVisibilityApis);
- router.use(metaDiffApis);
- router.use(cacheApis);
- router.use(apiTokenApis);
- router.use(hookFilterApis);
- router.use(swaggerApis);
- router.use(syncSourceApis);
- router.use(kanbanViewApis);
- router.use(mapViewApis);
+ router.use(columnController);
+ router.use(exportController);
+ router.use(dataController);
+ router.use(bulkDataAliasController);
+ router.use(dataAliasController);
+ router.use(dataAliasNestedController);
+ router.use(dataAliasExportController);
+ router.use(oldDataController);
+ router.use(sortController);
+ router.use(filterController);
+ router.use(viewColumnController);
+ router.use(gridViewController);
+ router.use(formViewColumnController);
+ router.use(publicDataController);
+ router.use(publicDataExportController);
+ router.use(publicMetaController);
+ router.use(gridViewColumnController);
+ router.use(tableController);
+ router.use(galleryViewController);
+ router.use(formViewController);
+ router.use(viewController);
+ router.use(attachmentController);
+ router.use(auditController);
+ router.use(hookController);
+ router.use(pluginController);
+ router.use(projectUserController);
+ router.use(orgUserController);
+ router.use(orgTokenController);
+ router.use(orgLicenseController);
+ router.use(sharedBaseController);
+ router.use(modelVisibilityController);
+ router.use(metaDiffController);
+ router.use(cacheController);
+ router.use(apiTokenController);
+ router.use(hookFilterController);
+ router.use(swaggerController);
+ router.use(syncSourceController);
+ router.use(kanbanViewController);
+ router.use(mapViewController);
- userApis(router);
+ userController(router);
const io = new Server(server, {
cors: {
@@ -133,15 +133,15 @@ export default function (router: Router, server) {
}).on('connection', (socket) => {
clients[socket.id] = socket;
const id = getHash(
- (process.env.NC_SERVER_UUID || Tele.id) +
+ (process.env.NC_SERVER_UUID || T.id) +
(socket?.handshake as any)?.user?.id
);
socket.on('page', (args) => {
- Tele.page({ ...args, id });
+ T.page({ ...args, id });
});
socket.on('event', (args) => {
- Tele.event({ ...args, id });
+ T.event({ ...args, id });
});
socket.on('subscribe', (room) => {
if (room in jobs) {
@@ -152,7 +152,7 @@ export default function (router: Router, server) {
});
});
- importApis(router, io, jobs);
+ importController(router, io, jobs);
}
function getHash(str) {
diff --git a/packages/nocodb/src/lib/meta/api/modelVisibilityApis.ts b/packages/nocodb/src/lib/meta/api/modelVisibilityApis.ts
deleted file mode 100644
index 26dc965bb1..0000000000
--- a/packages/nocodb/src/lib/meta/api/modelVisibilityApis.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import Model from '../../models/Model';
-import ModelRoleVisibility from '../../models/ModelRoleVisibility';
-import { Router } from 'express';
-import { Tele } from 'nc-help';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
-async function xcVisibilityMetaSetAll(req, res) {
- Tele.emit('evt', { evt_type: 'uiAcl:updated' });
- for (const d of req.body) {
- for (const role of Object.keys(d.disabled)) {
- const dataInDb = await ModelRoleVisibility.get({
- role,
- // fk_model_id: d.fk_model_id,
- fk_view_id: d.id,
- });
- if (dataInDb) {
- if (d.disabled[role]) {
- if (!dataInDb.disabled) {
- await ModelRoleVisibility.update(d.id, role, {
- disabled: d.disabled[role],
- });
- }
- } else {
- await dataInDb.delete();
- }
- } else if (d.disabled[role]) {
- await ModelRoleVisibility.insert({
- fk_view_id: d.id,
- disabled: d.disabled[role],
- role,
- });
- }
- }
- }
- Tele.emit('evt', { evt_type: 'uiAcl:updated' });
-
- res.json({ msg: 'success' });
-}
-
-// @ts-ignore
-export async function xcVisibilityMetaGet(
- projectId,
- _models: Model[] = null,
- includeM2M = true
- // type: 'table' | 'tableAndViews' | 'views' = 'table'
-) {
- // todo: move to
- const roles = ['owner', 'creator', 'viewer', 'editor', 'commenter', 'guest'];
-
- const defaultDisabled = roles.reduce((o, r) => ({ ...o, [r]: false }), {});
-
- let models =
- _models ||
- (await Model.list({
- project_id: projectId,
- base_id: undefined,
- }));
-
- models = includeM2M ? models : (models.filter((t) => !t.mm) as Model[]);
-
- const result = await models.reduce(async (_obj, model) => {
- const obj = await _obj;
- // obj[model.id] = {
- // tn: model.tn,
- // _tn: model._tn,
- // order: model.order,
- // fk_model_id: model.id,
- // id: model.id,
- // type: model.type,
- // disabled: { ...defaultDisabled }
- // };
- // if (type === 'tableAndViews') {
- const views = await model.getViews();
- for (const view of views) {
- obj[view.id] = {
- ptn: model.table_name,
- _ptn: model.title,
- ptype: model.type,
- tn: view.title,
- _tn: view.title,
- table_meta: model.meta,
- ...view,
- disabled: { ...defaultDisabled },
- };
- // }
- }
-
- return obj;
- }, Promise.resolve({}));
-
- const disabledList = await ModelRoleVisibility.list(projectId);
-
- for (const d of disabledList) {
- // if (d.fk_model_id) result[d.fk_model_id].disabled[d.role] = !!d.disabled;
- // else if (type === 'tableAndViews' && d.fk_view_id)
- if (result[d.fk_view_id])
- result[d.fk_view_id].disabled[d.role] = !!d.disabled;
- }
-
- return Object.values(result);
- // ?.sort(
- // (a: any, b: any) =>
- // (a.order || 0) - (b.order || 0) ||
- // (a?._tn || a?.tn)?.localeCompare(b?._tn || b?.tn)
- // );
-}
-
-const router = Router({ mergeParams: true });
-router.get(
- '/api/v1/db/meta/projects/:projectId/visibility-rules',
- metaApiMetrics,
- ncMetaAclMw(async (req, res) => {
- res.json(
- await xcVisibilityMetaGet(
- req.params.projectId,
- null,
- req.query.includeM2M === true || req.query.includeM2M === 'true'
- )
- );
- }, 'modelVisibilityList')
-);
-router.post(
- '/api/v1/db/meta/projects/:projectId/visibility-rules',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/VisibilityRuleReq'),
- ncMetaAclMw(xcVisibilityMetaSetAll, 'modelVisibilitySet')
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/orgTokenApis.ts b/packages/nocodb/src/lib/meta/api/orgTokenApis.ts
deleted file mode 100644
index 300a804c4e..0000000000
--- a/packages/nocodb/src/lib/meta/api/orgTokenApis.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { Request, Response, Router } from 'express';
-import { OrgUserRoles } from 'nocodb-sdk';
-import ApiToken from '../../models/ApiToken';
-import { Tele } from 'nc-help';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { NcError } from '../helpers/catchError';
-import getHandler from '../helpers/getHandler';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-import { apiTokenListEE } from './ee/orgTokenApis';
-import { getAjvValidatorMw } from './helpers';
-
-async function apiTokenList(req, res) {
- const fk_user_id = req.user.id;
- let includeUnmappedToken = false;
- if (req['user'].roles.includes(OrgUserRoles.SUPER_ADMIN)) {
- includeUnmappedToken = true;
- }
-
- res.json(
- new PagedResponseImpl(
- await ApiToken.listWithCreatedBy({
- ...req.query,
- fk_user_id,
- includeUnmappedToken,
- }),
- {
- ...req.query,
- count: await ApiToken.count({
- includeUnmappedToken,
- fk_user_id,
- }),
- }
- )
- );
-}
-
-export async function apiTokenCreate(req: Request, res: Response) {
- Tele.emit('evt', { evt_type: 'org:apiToken:created' });
- res.json(await ApiToken.insert({ ...req.body, fk_user_id: req['user'].id }));
-}
-
-export async function apiTokenDelete(req: Request, res: Response) {
- const fk_user_id = req['user'].id;
- const apiToken = await ApiToken.getByToken(req.params.token);
- if (
- !req['user'].roles.includes(OrgUserRoles.SUPER_ADMIN) &&
- apiToken.fk_user_id !== fk_user_id
- ) {
- NcError.notFound('Token not found');
- }
- Tele.emit('evt', { evt_type: 'org:apiToken:deleted' });
- res.json(await ApiToken.delete(req.params.token));
-}
-
-const router = Router({ mergeParams: true });
-
-router.get(
- '/api/v1/tokens',
- metaApiMetrics,
- ncMetaAclMw(getHandler(apiTokenList, apiTokenListEE), 'apiTokenList', {
- // allowedRoles: [OrgUserRoles.SUPER],
- blockApiTokenAccess: true,
- })
-);
-router.post(
- '/api/v1/tokens',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/ApiTokenReq'),
- ncMetaAclMw(apiTokenCreate, 'apiTokenCreate', {
- // allowedRoles: [OrgUserRoles.SUPER],
- blockApiTokenAccess: true,
- })
-);
-router.delete(
- '/api/v1/tokens/:token',
- metaApiMetrics,
- ncMetaAclMw(apiTokenDelete, 'apiTokenDelete', {
- // allowedRoles: [OrgUserRoles.SUPER],
- blockApiTokenAccess: true,
- })
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/orgUserApis.ts b/packages/nocodb/src/lib/meta/api/orgUserApis.ts
deleted file mode 100644
index 62c3cdf2aa..0000000000
--- a/packages/nocodb/src/lib/meta/api/orgUserApis.ts
+++ /dev/null
@@ -1,337 +0,0 @@
-import { Router } from 'express';
-import {
- AuditOperationSubTypes,
- AuditOperationTypes,
- PluginCategory,
-} from 'nocodb-sdk';
-import { v4 as uuidv4 } from 'uuid';
-import validator from 'validator';
-import { OrgUserRoles } from 'nocodb-sdk';
-import { NC_APP_SETTINGS } from '../../constants';
-import Audit from '../../models/Audit';
-import ProjectUser from '../../models/ProjectUser';
-import Store from '../../models/Store';
-import SyncSource from '../../models/SyncSource';
-import User from '../../models/User';
-import Noco from '../../Noco';
-import { MetaTable } from '../../utils/globals';
-import { Tele } from 'nc-help';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { NcError } from '../helpers/catchError';
-import { extractProps } from '../helpers/extractProps';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-import { randomTokenString } from '../helpers/stringHelpers';
-import { getAjvValidatorMw } from './helpers';
-import { sendInviteEmail } from './projectUserApis';
-
-async function userList(req, res) {
- res.json(
- new PagedResponseImpl(await User.list(req.query), {
- ...req.query,
- count: await User.count(req.query),
- })
- );
-}
-
-async function userUpdate(req, res) {
- const updateBody = extractProps(req.body, ['roles']);
-
- const user = await User.get(req.params.userId);
-
- if (user.roles.includes(OrgUserRoles.SUPER_ADMIN)) {
- NcError.badRequest('Cannot update super admin roles');
- }
-
- res.json(
- await User.update(req.params.userId, {
- ...updateBody,
- token_version: null,
- })
- );
-}
-
-async function userDelete(req, res) {
- const ncMeta = await Noco.ncMeta.startTransaction();
- try {
- const user = await User.get(req.params.userId, ncMeta);
-
- if (user.roles.includes(OrgUserRoles.SUPER_ADMIN)) {
- NcError.badRequest('Cannot delete super admin');
- }
-
- // delete project user entry and assign to super admin
- const projectUsers = await ProjectUser.getProjectsIdList(
- req.params.userId,
- ncMeta
- );
-
- // todo: clear cache
-
- // TODO: assign super admin as project owner
- for (const projectUser of projectUsers) {
- await ProjectUser.delete(
- projectUser.project_id,
- projectUser.fk_user_id,
- ncMeta
- );
- }
-
- // delete sync source entry
- await SyncSource.deleteByUserId(req.params.userId, ncMeta);
-
- // delete user
- await User.delete(req.params.userId, ncMeta);
- await ncMeta.commit();
- } catch (e) {
- await ncMeta.rollback(e);
- throw e;
- }
-
- res.json({ msg: 'success' });
-}
-
-async function userAdd(req, res, next) {
- // allow only viewer or creator role
- if (
- req.body.roles &&
- ![OrgUserRoles.VIEWER, OrgUserRoles.CREATOR].includes(req.body.roles)
- ) {
- NcError.badRequest('Invalid role');
- }
-
- // extract emails from request body
- const emails = (req.body.email || '')
- .toLowerCase()
- .split(/\s*,\s*/)
- .map((v) => v.trim());
-
- // check for invalid emails
- const invalidEmails = emails.filter((v) => !validator.isEmail(v));
-
- if (!emails.length) {
- return NcError.badRequest('Invalid email address');
- }
- if (invalidEmails.length) {
- NcError.badRequest('Invalid email address : ' + invalidEmails.join(', '));
- }
-
- const invite_token = uuidv4();
- const error = [];
-
- for (const email of emails) {
- // add user to project if user already exist
- const user = await User.getByEmail(email);
-
- if (user) {
- NcError.badRequest('User already exist');
- } else {
- try {
- // create new user with invite token
- await User.insert({
- invite_token,
- invite_token_expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
- email,
- roles: req.body.roles || OrgUserRoles.VIEWER,
- token_version: randomTokenString(),
- });
-
- const count = await User.count();
- Tele.emit('evt', { evt_type: 'org:user:invite', count });
-
- await Audit.insert({
- op_type: AuditOperationTypes.ORG_USER,
- op_sub_type: AuditOperationSubTypes.INVITE,
- user: req.user.email,
- description: `invited ${email} to ${req.params.projectId} project `,
- ip: req.clientIp,
- });
- // in case of single user check for smtp failure
- // and send back token if failed
- if (
- emails.length === 1 &&
- !(await sendInviteEmail(email, invite_token, req))
- ) {
- return res.json({ invite_token, email });
- } else {
- sendInviteEmail(email, invite_token, req);
- }
- } catch (e) {
- console.log(e);
- if (emails.length === 1) {
- return next(e);
- } else {
- error.push({ email, error: e.message });
- }
- }
- }
- }
-
- if (emails.length === 1) {
- res.json({
- msg: 'success',
- });
- } else {
- return res.json({ invite_token, emails, error });
- }
-}
-
-async function userSettings(_req, _res): Promise {
- NcError.notImplemented();
-}
-
-async function userInviteResend(req, res): Promise {
- const user = await User.get(req.params.userId);
-
- if (!user) {
- NcError.badRequest(`User with id '${req.params.userId}' not found`);
- }
-
- const invite_token = uuidv4();
-
- await User.update(user.id, {
- invite_token,
- invite_token_expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
- });
-
- const pluginData = await Noco.ncMeta.metaGet2(null, null, MetaTable.PLUGIN, {
- category: PluginCategory.EMAIL,
- active: true,
- });
-
- if (!pluginData) {
- NcError.badRequest(
- `No Email Plugin is found. Please go to App Store to configure first or copy the invitation URL to users instead.`
- );
- }
-
- await sendInviteEmail(user.email, invite_token, req);
-
- await Audit.insert({
- op_type: AuditOperationTypes.ORG_USER,
- op_sub_type: AuditOperationSubTypes.RESEND_INVITE,
- user: user.email,
- description: `resent a invite to ${user.email} `,
- ip: req.clientIp,
- });
-
- res.json({ msg: 'success' });
-}
-
-async function generateResetUrl(req, res) {
- const user = await User.get(req.params.userId);
-
- if (!user) {
- NcError.badRequest(`User with id '${req.params.userId}' not found`);
- }
- const token = uuidv4();
- await User.update(user.id, {
- email: user.email,
- reset_password_token: token,
- reset_password_expires: new Date(Date.now() + 60 * 60 * 1000),
- token_version: null,
- });
-
- res.json({
- reset_password_token: token,
- reset_password_url: req.ncSiteUrl + `/auth/password/reset/${token}`,
- });
-}
-
-async function appSettingsGet(_req, res) {
- let settings = {};
- try {
- settings = JSON.parse((await Store.get(NC_APP_SETTINGS))?.value);
- } catch {}
- res.json(settings);
-}
-
-async function appSettingsSet(req, res) {
- await Store.saveOrUpdate({
- value: JSON.stringify(req.body),
- key: NC_APP_SETTINGS,
- });
-
- res.json({ msg: 'Settings saved' });
-}
-
-const router = Router({ mergeParams: true });
-router.get(
- '/api/v1/users',
- metaApiMetrics,
- ncMetaAclMw(userList, 'userList', {
- allowedRoles: [OrgUserRoles.SUPER_ADMIN],
- blockApiTokenAccess: true,
- })
-);
-router.patch(
- '/api/v1/users/:userId',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/OrgUserReq'),
- ncMetaAclMw(userUpdate, 'userUpdate', {
- allowedRoles: [OrgUserRoles.SUPER_ADMIN],
- blockApiTokenAccess: true,
- })
-);
-router.delete(
- '/api/v1/users/:userId',
- metaApiMetrics,
- ncMetaAclMw(userDelete, 'userDelete', {
- allowedRoles: [OrgUserRoles.SUPER_ADMIN],
- blockApiTokenAccess: true,
- })
-);
-router.post(
- '/api/v1/users',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/OrgUserReq'),
- ncMetaAclMw(userAdd, 'userAdd', {
- allowedRoles: [OrgUserRoles.SUPER_ADMIN],
- blockApiTokenAccess: true,
- })
-);
-router.post(
- '/api/v1/users/settings',
- metaApiMetrics,
- ncMetaAclMw(userSettings, 'userSettings', {
- allowedRoles: [OrgUserRoles.SUPER_ADMIN],
- blockApiTokenAccess: true,
- })
-);
-router.post(
- '/api/v1/users/:userId/resend-invite',
- metaApiMetrics,
- ncMetaAclMw(userInviteResend, 'userInviteResend', {
- allowedRoles: [OrgUserRoles.SUPER_ADMIN],
- blockApiTokenAccess: true,
- })
-);
-
-router.post(
- '/api/v1/users/:userId/generate-reset-url',
- metaApiMetrics,
- ncMetaAclMw(generateResetUrl, 'generateResetUrl', {
- allowedRoles: [OrgUserRoles.SUPER_ADMIN],
- blockApiTokenAccess: true,
- })
-);
-
-router.get(
- '/api/v1/app-settings',
- metaApiMetrics,
- ncMetaAclMw(appSettingsGet, 'appSettingsGet', {
- allowedRoles: [OrgUserRoles.SUPER_ADMIN],
- blockApiTokenAccess: true,
- })
-);
-
-router.post(
- '/api/v1/app-settings',
- metaApiMetrics,
- ncMetaAclMw(appSettingsSet, 'appSettingsSet', {
- allowedRoles: [OrgUserRoles.SUPER_ADMIN],
- blockApiTokenAccess: true,
- })
-);
-
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/projectApis.ts b/packages/nocodb/src/lib/meta/api/projectApis.ts
deleted file mode 100644
index 5f45bbb3da..0000000000
--- a/packages/nocodb/src/lib/meta/api/projectApis.ts
+++ /dev/null
@@ -1,287 +0,0 @@
-import { Request, Response } from 'express';
-import { OrgUserRoles, ProjectType } from 'nocodb-sdk';
-import Project from '../../models/Project';
-import { ProjectListType } from 'nocodb-sdk';
-import DOMPurify from 'isomorphic-dompurify';
-import { packageVersion } from '../../utils/packageVersion';
-import { Tele } from 'nc-help';
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-import syncMigration from '../helpers/syncMigration';
-import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import ProjectUser from '../../models/ProjectUser';
-import { customAlphabet } from 'nanoid';
-import Noco from '../../Noco';
-import isDocker from 'is-docker';
-import { NcError } from '../helpers/catchError';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { extractPropsAndSanitize } from '../helpers/extractProps';
-import NcConfigFactory from '../../utils/NcConfigFactory';
-import { promisify } from 'util';
-import { getAjvValidatorMw, populateMeta } from './helpers';
-import Filter from '../../models/Filter';
-
-const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4);
-
-// // Project CRUD
-
-export async function projectGet(
- req: Request,
- res: Response
-) {
- const project = await Project.getWithInfo(req.params.projectId);
-
- // delete datasource connection details
- project.bases?.forEach((b) => {
- ['config'].forEach((k) => delete b[k]);
- });
-
- res.json(project);
-}
-
-export async function projectUpdate(
- req: Request,
- res: Response
-) {
- const project = await Project.getWithInfo(req.params.projectId);
-
- const data: Partial = extractPropsAndSanitize(req?.body, [
- 'title',
- 'meta',
- 'color',
- ]);
-
- if (
- data?.title &&
- project.title !== data.title &&
- (await Project.getByTitle(data.title))
- ) {
- NcError.badRequest('Project title already in use');
- }
-
- const result = await Project.update(req.params.projectId, data);
- Tele.emit('evt', { evt_type: 'project:update' });
- res.json(result);
-}
-
-export async function projectList(
- req: Request & { user: { id: string; roles: string } },
- res: Response,
- next
-) {
- try {
- const projects = req.user?.roles?.includes(OrgUserRoles.SUPER_ADMIN)
- ? await Project.list(req.query)
- : await ProjectUser.getProjectsList(req.user.id, req.query);
-
- res // todo: pagination
- .json(
- new PagedResponseImpl(projects as ProjectType[], {
- count: projects.length,
- limit: projects.length,
- })
- );
- } catch (e) {
- console.log(e);
- next(e);
- }
-}
-
-export async function projectDelete(
- req: Request,
- res: Response
-) {
- const result = await Project.softDelete(req.params.projectId);
- Tele.emit('evt', { evt_type: 'project:deleted' });
- res.json(result);
-}
-
-//
-//
-
-async function projectCreate(req: Request, res) {
- const projectBody = req.body;
- if (!projectBody.external) {
- const ranId = nanoid();
- projectBody.prefix = `nc_${ranId}__`;
- projectBody.is_meta = true;
- if (process.env.NC_MINIMAL_DBS) {
- // if env variable NC_MINIMAL_DBS is set, then create a SQLite file/connection for each project
- // each file will be named as nc_.db
- const fs = require('fs');
- const toolDir = NcConfigFactory.getToolDir();
- const nanoidv2 = customAlphabet(
- '1234567890abcdefghijklmnopqrstuvwxyz',
- 14
- );
- if (!(await promisify(fs.exists)(`${toolDir}/nc_minimal_dbs`))) {
- await promisify(fs.mkdir)(`${toolDir}/nc_minimal_dbs`);
- }
- const dbId = nanoidv2();
- const projectTitle = DOMPurify.sanitize(projectBody.title);
- projectBody.prefix = '';
- projectBody.bases = [
- {
- type: 'sqlite3',
- config: {
- client: 'sqlite3',
- connection: {
- client: 'sqlite3',
- database: projectTitle,
- connection: {
- filename: `${toolDir}/nc_minimal_dbs/${projectTitle}_${dbId}.db`,
- },
- },
- },
- inflection_column: 'camelize',
- inflection_table: 'camelize',
- },
- ];
- } else {
- const db = Noco.getConfig().meta?.db;
- projectBody.bases = [
- {
- type: db?.client,
- config: null,
- is_meta: true,
- inflection_column: 'camelize',
- inflection_table: 'camelize',
- },
- ];
- }
- } else {
- if (process.env.NC_CONNECT_TO_EXTERNAL_DB_DISABLED) {
- NcError.badRequest('Connecting to external db is disabled');
- }
- projectBody.is_meta = false;
- }
-
- if (projectBody?.title.length > 50) {
- NcError.badRequest('Project title exceeds 50 characters');
- }
-
- if (await Project.getByTitle(projectBody?.title)) {
- NcError.badRequest('Project title already in use');
- }
-
- projectBody.title = DOMPurify.sanitize(projectBody.title);
-
- const project = await Project.createProject(projectBody);
- await ProjectUser.insert({
- fk_user_id: (req as any).user.id,
- project_id: project.id,
- roles: 'owner',
- });
-
- await syncMigration(project);
-
- // populate metadata if existing table
- for (const base of await project.getBases()) {
- const info = await populateMeta(base, project);
-
- Tele.emit('evt_api_created', info);
- delete base.config;
- }
-
- Tele.emit('evt', {
- evt_type: 'project:created',
- xcdb: !projectBody.external,
- });
-
- Tele.emit('evt', { evt_type: 'project:rest' });
-
- res.json(project);
-}
-
-export async function projectInfoGet(_req, res) {
- res.json({
- Node: process.version,
- Arch: process.arch,
- Platform: process.platform,
- Docker: isDocker(),
- RootDB: Noco.getConfig()?.meta?.db?.client,
- PackageVersion: packageVersion,
- });
-}
-
-export async function projectCost(req, res) {
- let cost = 0;
- const project = await Project.getWithInfo(req.params.projectId);
-
- for (const base of project.bases) {
- const sqlClient = await NcConnectionMgrv2.getSqlClient(base);
- const userCount = await ProjectUser.getUsersCount(req.query);
- const recordCount = (await sqlClient.totalRecords())?.data.TotalRecords;
-
- if (recordCount > 100000) {
- // 36,000 or $79/user/month
- cost = Math.max(36000, 948 * userCount);
- } else if (recordCount > 50000) {
- // $36,000 or $50/user/month
- cost = Math.max(36000, 600 * userCount);
- } else if (recordCount > 10000) {
- // $240/user/yr
- cost = Math.min(240 * userCount, 36000);
- } else if (recordCount > 1000) {
- // $120/user/yr
- cost = Math.min(120 * userCount, 36000);
- }
- }
-
- Tele.event({
- event: 'a:project:cost',
- data: {
- cost,
- },
- });
-
- res.json({ cost });
-}
-
-export async function hasEmptyOrNullFilters(req, res) {
- res.json(await Filter.hasEmptyOrNullFilters(req.params.projectId));
-}
-
-export default (router) => {
- router.get(
- '/api/v1/db/meta/projects/:projectId/info',
- metaApiMetrics,
- ncMetaAclMw(projectInfoGet, 'projectInfoGet')
- );
- router.get(
- '/api/v1/db/meta/projects/:projectId',
- metaApiMetrics,
- ncMetaAclMw(projectGet, 'projectGet')
- );
- router.patch(
- '/api/v1/db/meta/projects/:projectId',
- metaApiMetrics,
- ncMetaAclMw(projectUpdate, 'projectUpdate')
- );
- router.get(
- '/api/v1/db/meta/projects/:projectId/cost',
- metaApiMetrics,
- ncMetaAclMw(projectCost, 'projectCost')
- );
- router.delete(
- '/api/v1/db/meta/projects/:projectId',
- metaApiMetrics,
- ncMetaAclMw(projectDelete, 'projectDelete')
- );
- router.post(
- '/api/v1/db/meta/projects',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/ProjectReq'),
- ncMetaAclMw(projectCreate, 'projectCreate')
- );
- router.get(
- '/api/v1/db/meta/projects',
- metaApiMetrics,
- ncMetaAclMw(projectList, 'projectList')
- );
- router.get(
- '/api/v1/db/meta/projects/:projectId/has-empty-or-null-filters',
- metaApiMetrics,
- ncMetaAclMw(hasEmptyOrNullFilters, 'hasEmptyOrNullFilters')
- );
-};
diff --git a/packages/nocodb/src/lib/meta/api/projectUserApis.ts b/packages/nocodb/src/lib/meta/api/projectUserApis.ts
deleted file mode 100644
index 3f0610fee6..0000000000
--- a/packages/nocodb/src/lib/meta/api/projectUserApis.ts
+++ /dev/null
@@ -1,333 +0,0 @@
-import { OrgUserRoles } from 'nocodb-sdk';
-import { Tele } from 'nc-help';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { Router } from 'express';
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-import ProjectUser from '../../models/ProjectUser';
-import validator from 'validator';
-import { NcError } from '../helpers/catchError';
-import { v4 as uuidv4 } from 'uuid';
-import User from '../../models/User';
-import Audit from '../../models/Audit';
-import NocoCache from '../../cache/NocoCache';
-import { CacheGetType, CacheScope, MetaTable } from '../../utils/globals';
-import * as ejs from 'ejs';
-import NcPluginMgrv2 from '../helpers/NcPluginMgrv2';
-import Noco from '../../Noco';
-import { PluginCategory } from 'nocodb-sdk';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { randomTokenString } from '../helpers/stringHelpers';
-import { getAjvValidatorMw } from './helpers';
-
-async function userList(req, res) {
- res.json({
- users: new PagedResponseImpl(
- await ProjectUser.getUsersList({
- ...req.query,
- project_id: req.params.projectId,
- }),
- {
- ...req.query,
- count: await ProjectUser.getUsersCount(req.query),
- }
- ),
- });
-}
-
-async function userInvite(req, res, next): Promise {
- const emails = (req.body.email || '')
- .toLowerCase()
- .split(/\s*,\s*/)
- .map((v) => v.trim());
-
- // check for invalid emails
- const invalidEmails = emails.filter((v) => !validator.isEmail(v));
- if (!emails.length) {
- return NcError.badRequest('Invalid email address');
- }
- if (invalidEmails.length) {
- NcError.badRequest('Invalid email address : ' + invalidEmails.join(', '));
- }
-
- const invite_token = uuidv4();
- const error = [];
-
- for (const email of emails) {
- // add user to project if user already exist
- const user = await User.getByEmail(email);
-
- if (user) {
- // check if this user has been added to this project
- const projectUser = await ProjectUser.get(req.params.projectId, user.id);
- if (projectUser) {
- NcError.badRequest(
- `${user.email} with role ${projectUser.roles} already exists in this project`
- );
- }
-
- await ProjectUser.insert({
- project_id: req.params.projectId,
- fk_user_id: user.id,
- roles: req.body.roles || 'editor',
- });
-
- const cachedUser = await NocoCache.get(
- `${CacheScope.USER}:${email}___${req.params.projectId}`,
- CacheGetType.TYPE_OBJECT
- );
-
- if (cachedUser) {
- cachedUser.roles = req.body.roles || 'editor';
- await NocoCache.set(
- `${CacheScope.USER}:${email}___${req.params.projectId}`,
- cachedUser
- );
- }
-
- await Audit.insert({
- project_id: req.params.projectId,
- op_type: 'AUTHENTICATION',
- op_sub_type: 'INVITE',
- user: req.user.email,
- description: `invited ${email} to ${req.params.projectId} project `,
- ip: req.clientIp,
- });
- } else {
- try {
- // create new user with invite token
- const { id } = await User.insert({
- invite_token,
- invite_token_expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
- email,
- roles: OrgUserRoles.VIEWER,
- token_version: randomTokenString(),
- });
-
- // add user to project
- await ProjectUser.insert({
- project_id: req.params.projectId,
- fk_user_id: id,
- roles: req.body.roles,
- });
-
- const count = await User.count();
- Tele.emit('evt', { evt_type: 'project:invite', count });
-
- await Audit.insert({
- project_id: req.params.projectId,
- op_type: 'AUTHENTICATION',
- op_sub_type: 'INVITE',
- user: req.user.email,
- description: `invited ${email} to ${req.params.projectId} project `,
- ip: req.clientIp,
- });
- // in case of single user check for smtp failure
- // and send back token if failed
- if (
- emails.length === 1 &&
- !(await sendInviteEmail(email, invite_token, req))
- ) {
- return res.json({ invite_token, email });
- } else {
- sendInviteEmail(email, invite_token, req);
- }
- } catch (e) {
- console.log(e);
- if (emails.length === 1) {
- return next(e);
- } else {
- error.push({ email, error: e.message });
- }
- }
- }
- }
-
- if (emails.length === 1) {
- res.json({
- msg: 'success',
- });
- } else {
- return res.json({ invite_token, emails, error });
- }
-}
-
-// @ts-ignore
-async function projectUserUpdate(req, res, next): Promise {
- if (!req?.body?.project_id) {
- return next(new Error('Missing project id in request body.'));
- }
-
- if (
- req.session?.passport?.user?.roles?.owner &&
- req.session?.passport?.user?.id === req.params.userId &&
- req.body.roles.indexOf('owner') === -1
- ) {
- NcError.badRequest("Super admin can't remove Super role themselves");
- }
- try {
- const user = await User.get(req.params.userId);
-
- if (!user) {
- NcError.badRequest(`User with id '${req.params.userId}' doesn't exist`);
- }
-
- // todo: handle roles which contains super
- if (
- !req.session?.passport?.user?.roles?.owner &&
- req.body.roles.indexOf('owner') > -1
- ) {
- NcError.forbidden('Insufficient privilege to add super admin role.');
- }
-
- await ProjectUser.update(
- req.params.projectId,
- req.params.userId,
- req.body.roles
- );
-
- await Audit.insert({
- op_type: 'AUTHENTICATION',
- op_sub_type: 'ROLES_MANAGEMENT',
- user: req.user.email,
- description: `updated roles for ${user.email} with ${req.body.roles} `,
- ip: req.clientIp,
- });
-
- res.json({
- msg: 'User details updated successfully',
- });
- } catch (e) {
- next(e);
- }
-}
-
-async function projectUserDelete(req, res): Promise {
- const project_id = req.params.projectId;
-
- if (req.session?.passport?.user?.id === req.params.userId) {
- NcError.badRequest("Admin can't delete themselves!");
- }
-
- if (!req.session?.passport?.user?.roles?.owner) {
- const user = await User.get(req.params.userId);
- if (user.roles?.split(',').includes('super'))
- NcError.forbidden('Insufficient privilege to delete a super admin user.');
-
- const projectUser = await ProjectUser.get(project_id, req.params.userId);
- if (projectUser?.roles?.split(',').includes('super'))
- NcError.forbidden('Insufficient privilege to delete a owner user.');
- }
-
- await ProjectUser.delete(project_id, req.params.userId);
- res.json({
- msg: 'success',
- });
-}
-
-async function projectUserInviteResend(req, res): Promise {
- const user = await User.get(req.params.userId);
-
- if (!user) {
- NcError.badRequest(`User with id '${req.params.userId}' not found`);
- }
-
- req.body.roles = user.roles;
- const invite_token = uuidv4();
-
- await User.update(user.id, {
- invite_token,
- invite_token_expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
- });
-
- const pluginData = await Noco.ncMeta.metaGet2(null, null, MetaTable.PLUGIN, {
- category: PluginCategory.EMAIL,
- active: true,
- });
-
- if (!pluginData) {
- NcError.badRequest(
- `No Email Plugin is found. Please go to App Store to configure first or copy the invitation URL to users instead.`
- );
- }
-
- await sendInviteEmail(user.email, invite_token, req);
-
- await Audit.insert({
- op_type: 'AUTHENTICATION',
- op_sub_type: 'RESEND_INVITE',
- user: user.email,
- description: `resent a invite to ${user.email} `,
- ip: req.clientIp,
- project_id: req.params.projectId,
- });
-
- res.json({ msg: 'success' });
-}
-
-export async function sendInviteEmail(
- email: string,
- token: string,
- req: any
-): Promise {
- try {
- const template = (await import('./userApi/ui/emailTemplates/invite'))
- .default;
-
- const emailAdapter = await NcPluginMgrv2.emailAdapter();
-
- if (emailAdapter) {
- await emailAdapter.mailSend({
- to: email,
- subject: 'Verify email',
- html: ejs.render(template, {
- signupLink: `${req.ncSiteUrl}${
- Noco.getConfig()?.dashboardPath
- }#/signup/${token}`,
- projectName: req.body?.projectName,
- roles: (req.body?.roles || '')
- .split(',')
- .map((r) => r.replace(/^./, (m) => m.toUpperCase()))
- .join(', '),
- adminEmail: req.session?.passport?.user?.email,
- }),
- });
- return true;
- }
- } catch (e) {
- console.log(
- 'Warning : `mailSend` failed, Please configure emailClient configuration.',
- e.message
- );
- throw e;
- }
-}
-
-const router = Router({ mergeParams: true });
-router.get(
- '/api/v1/db/meta/projects/:projectId/users',
- metaApiMetrics,
- ncMetaAclMw(userList, 'userList')
-);
-router.post(
- '/api/v1/db/meta/projects/:projectId/users',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/ProjectUserReq'),
- ncMetaAclMw(userInvite, 'userInvite')
-);
-router.patch(
- '/api/v1/db/meta/projects/:projectId/users/:userId',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/ProjectUserReq'),
- ncMetaAclMw(projectUserUpdate, 'projectUserUpdate')
-);
-router.delete(
- '/api/v1/db/meta/projects/:projectId/users/:userId',
- metaApiMetrics,
- ncMetaAclMw(projectUserDelete, 'projectUserDelete')
-);
-router.post(
- '/api/v1/db/meta/projects/:projectId/users/:userId/resend-invite',
- metaApiMetrics,
- ncMetaAclMw(projectUserInviteResend, 'projectUserInviteResend')
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/publicApis/index.ts b/packages/nocodb/src/lib/meta/api/publicApis/index.ts
deleted file mode 100644
index 60028a421f..0000000000
--- a/packages/nocodb/src/lib/meta/api/publicApis/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import publicDataApis from './publicDataApis';
-import publicDataExportApis from './publicDataExportApis';
-import publicMetaApis from './publicMetaApis';
-
-export { publicDataApis, publicDataExportApis, publicMetaApis };
diff --git a/packages/nocodb/src/lib/meta/api/publicApis/publicDataApis.ts b/packages/nocodb/src/lib/meta/api/publicApis/publicDataApis.ts
deleted file mode 100644
index bb678b10f3..0000000000
--- a/packages/nocodb/src/lib/meta/api/publicApis/publicDataApis.ts
+++ /dev/null
@@ -1,472 +0,0 @@
-import { Request, Response, Router } from 'express';
-import Model from '../../../models/Model';
-import { nocoExecute } from 'nc-help';
-import Base from '../../../models/Base';
-import NcConnectionMgrv2 from '../../../utils/common/NcConnectionMgrv2';
-import { PagedResponseImpl } from '../../helpers/PagedResponse';
-import View from '../../../models/View';
-import catchError, { NcError } from '../../helpers/catchError';
-import multer from 'multer';
-import { ErrorMessages, UITypes, ViewTypes } from 'nocodb-sdk';
-import Column from '../../../models/Column';
-import LinkToAnotherRecordColumn from '../../../models/LinkToAnotherRecordColumn';
-import NcPluginMgrv2 from '../../helpers/NcPluginMgrv2';
-import path from 'path';
-import { nanoid } from 'nanoid';
-import { mimeIcons } from '../../../utils/mimeTypes';
-import slash from 'slash';
-import { sanitizeUrlPath } from '../attachmentApis';
-import getAst from '../../../db/sql-data-mapper/lib/sql/helpers/getAst';
-import { getColumnByIdOrName } from '../dataApis/helpers';
-import { NC_ATTACHMENT_FIELD_SIZE } from '../../../constants';
-
-export async function dataList(req: Request, res: Response) {
- try {
- const view = await View.getByUUID(req.params.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 !== req.headers?.['xc-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 = { ...req.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: req.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
- }
-
- res.json({
- data: new PagedResponseImpl(data, { ...req.query, count }),
- });
- } catch (e) {
- console.log(e);
- res.status(500).json({ msg: e.message });
- }
-}
-
-// todo: Handle the error case where view doesnt belong to model
-async function groupedDataList(req: Request, res: Response) {
- try {
- const view = await View.getByUUID(req.params.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 !== req.headers?.['xc-password']) {
- return NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
- }
-
- const model = await Model.getByIdOrName({
- id: view?.fk_model_id,
- });
-
- res.json(await getGroupedDataList(model, view, req));
- } catch (e) {
- console.log(e);
- res.status(500).json({ msg: e.message });
- }
-}
-
-async function getGroupedDataList(model, view: View, req) {
- 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({ model, query: req.query, view });
-
- const listArgs: any = { ...req.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: req.params.columnId,
- });
- data = await nocoExecute(
- { key: 1, value: requestObj },
- groupedData,
- {},
- listArgs
- );
- const countArr = await baseModel.groupedListCount({
- ...listArgs,
- groupColumnId: req.params.columnId,
- });
- 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, {
- ...req.query,
- count: count,
- });
- return item;
- });
- } catch (e) {
- // show empty result instead of throwing error here
- // e.g. search some text in a numeric field
- }
- return data;
-}
-
-async function dataInsert(
- req: Request & { files: any[] },
- res: Response,
- next
-) {
- const view = await View.getByUUID(req.params.sharedViewUuid);
-
- if (!view) return next(new Error('Not found'));
- if (view.type !== ViewTypes.FORM) return next(new Error('Not found'));
-
- if (view.password && view.password !== req.headers?.['xc-password']) {
- return res.status(403).json(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 = req.body?.data;
-
- 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 req.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 = `${(req as any).ncSiteUrl}/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);
- }
-
- res.json(await baseModel.nestedInsert(insertObject, null));
-}
-
-async function relDataList(req, res) {
- const view = await View.getByUUID(req.params.sharedViewUuid);
-
- if (!view) NcError.notFound('Not found');
- if (view.type !== ViewTypes.FORM) NcError.notFound('Not found');
-
- if (view.password && view.password !== req.headers?.['xc-password']) {
- NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
- }
-
- const column = await Column.get({ colId: req.params.columnId });
- const colOptions = await column.getColOptions();
-
- 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: req.query,
- model,
- extractOnlyPrimaries: true,
- });
-
- let data = [];
- let count = 0;
- try {
- data = data = await nocoExecute(
- requestObj,
- await baseModel.list(req.query),
- {},
- req.query
- );
- count = await baseModel.count(req.query);
- } catch (e) {
- // show empty result instead of throwing error here
- // e.g. search some text in a numeric field
- }
-
- res.json(new PagedResponseImpl(data, { ...req.query, count }));
-}
-
-export async function publicMmList(req: Request, res: Response) {
- const view = await View.getByUUID(req.params.sharedViewUuid);
-
- if (!view) NcError.notFound('Not found');
- if (view.type !== ViewTypes.GRID) NcError.notFound('Not found');
-
- if (view.password && view.password !== req.headers?.['xc-password']) {
- NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
- }
-
- const column = await getColumnByIdOrName(
- req.params.colId,
- 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: req.params.colId,
- parentId: req.params.rowId,
- },
- args
- );
- },
- },
- {},
-
- { nested: { [key]: req.query } }
- )
- )?.[key];
-
- const count: any = await baseModel.mmListCount({
- colId: req.params.colId,
- parentId: req.params.rowId,
- });
-
- res.json(new PagedResponseImpl(data, { ...req.query, count }));
-}
-
-export async function publicHmList(req: Request, res: Response) {
- const view = await View.getByUUID(req.params.sharedViewUuid);
-
- if (!view) NcError.notFound('Not found');
- if (view.type !== ViewTypes.GRID) NcError.notFound('Not found');
-
- if (view.password && view.password !== req.headers?.['xc-password']) {
- NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
- }
-
- const column = await getColumnByIdOrName(
- req.params.colId,
- 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: req.params.colId,
- id: req.params.rowId,
- },
- args
- );
- },
- },
- {},
- { nested: { [key]: req.query } }
- )
- )?.[key];
-
- const count = await baseModel.hmListCount({
- colId: req.params.colId,
- id: req.params.rowId,
- });
-
- res.json(new PagedResponseImpl(data, { ...req.query, count }));
-}
-
-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;
diff --git a/packages/nocodb/src/lib/meta/api/sharedBaseApis.ts b/packages/nocodb/src/lib/meta/api/sharedBaseApis.ts
deleted file mode 100644
index c232327aa8..0000000000
--- a/packages/nocodb/src/lib/meta/api/sharedBaseApis.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import { Router } from 'express';
-import { Tele } from 'nc-help';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { v4 as uuidv4 } from 'uuid';
-import Project from '../../models/Project';
-import { NcError } from '../helpers/catchError';
-import { getAjvValidatorMw } from './helpers';
-// todo: load from config
-const config = {
- dashboardPath: '/nc',
-};
-
-async function createSharedBaseLink(req, res): Promise {
- const project = await Project.get(req.params.projectId);
-
- let roles = req.body?.roles;
- if (!roles || (roles !== 'editor' && roles !== 'viewer')) {
- roles = 'viewer';
- }
-
- if (!project) {
- NcError.badRequest('Invalid project id');
- }
- const data: any = {
- uuid: uuidv4(),
- password: req.body?.password,
- roles,
- };
-
- await Project.update(project.id, data);
-
- data.url = `${req.ncSiteUrl}${config.dashboardPath}#/nc/base/${data.uuid}`;
- delete data.password;
- Tele.emit('evt', { evt_type: 'sharedBase:generated-link' });
- res.json(data);
-}
-async function updateSharedBaseLink(req, res): Promise {
- const project = await Project.get(req.params.projectId);
-
- let roles = req.body?.roles;
- if (!roles || (roles !== 'editor' && roles !== 'viewer')) {
- roles = 'viewer';
- }
-
- if (!project) {
- NcError.badRequest('Invalid project id');
- }
- const data: any = {
- uuid: project.uuid || uuidv4(),
- password: req.body?.password,
- roles,
- };
-
- await Project.update(project.id, data);
-
- data.url = `${req.ncSiteUrl}${config.dashboardPath}#/nc/base/${data.uuid}`;
- delete data.password;
- Tele.emit('evt', { evt_type: 'sharedBase:generated-link' });
- res.json(data);
-}
-
-async function disableSharedBaseLink(req, res): Promise {
- const project = await Project.get(req.params.projectId);
-
- if (!project) {
- NcError.badRequest('Invalid project id');
- }
- const data: any = {
- uuid: null,
- };
-
- await Project.update(project.id, data);
- Tele.emit('evt', { evt_type: 'sharedBase:disable-link' });
- res.json({ uuid: null });
-}
-
-async function getSharedBaseLink(req, res): Promise {
- const project = await Project.get(req.params.projectId);
-
- if (!project) {
- NcError.badRequest('Invalid project id');
- }
- const data: any = {
- uuid: project.uuid,
- roles: project.roles,
- };
- if (data.uuid)
- data.url = `${req.ncSiteUrl}${config.dashboardPath}#/nc/base/${data.shared_base_id}`;
-
- res.json(data);
-}
-
-const router = Router({ mergeParams: true });
-router.get(
- '/api/v1/db/meta/projects/:projectId/shared',
- ncMetaAclMw(getSharedBaseLink, 'getSharedBaseLink')
-);
-router.post(
- '/api/v1/db/meta/projects/:projectId/shared',
- getAjvValidatorMw('swagger.json#/components/schemas/SharedBaseReq'),
- ncMetaAclMw(createSharedBaseLink, 'createSharedBaseLink')
-);
-router.patch(
- '/api/v1/db/meta/projects/:projectId/shared',
- getAjvValidatorMw('swagger.json#/components/schemas/SharedBaseReq'),
- ncMetaAclMw(updateSharedBaseLink, 'updateSharedBaseLink')
-);
-router.delete(
- '/api/v1/db/meta/projects/:projectId/shared',
- ncMetaAclMw(disableSharedBaseLink, 'disableSharedBaseLink')
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/sortApis.ts b/packages/nocodb/src/lib/meta/api/sortApis.ts
deleted file mode 100644
index 5341d07cba..0000000000
--- a/packages/nocodb/src/lib/meta/api/sortApis.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { Request, Response, Router } from 'express';
-// @ts-ignore
-import Model from '../../models/Model';
-import { Tele } from 'nc-help';
-// @ts-ignore
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-import { SortListType, SortType, TableType } from 'nocodb-sdk';
-// @ts-ignore
-import ProjectMgrv2 from '../../db/sql-mgr/v2/ProjectMgrv2';
-// @ts-ignore
-import Project from '../../models/Project';
-import Sort from '../../models/Sort';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
-
-// @ts-ignore
-export async function sortGet(req: Request, res: Response) {}
-
-// @ts-ignore
-export async function sortList(
- req: Request,
- res: Response
-) {
- const sortList = await Sort.list({ viewId: req.params.viewId });
- res.json({
- sorts: new PagedResponseImpl(sortList),
- });
-}
-
-// @ts-ignore
-export async function sortCreate(req: Request, res) {
- const sort = await Sort.insert({
- ...req.body,
- fk_view_id: req.params.viewId,
- } as Sort);
- Tele.emit('evt', { evt_type: 'sort:created' });
- res.json(sort);
-}
-
-export async function sortUpdate(req, res) {
- const sort = await Sort.update(req.params.sortId, req.body);
- Tele.emit('evt', { evt_type: 'sort:updated' });
- res.json(sort);
-}
-
-export async function sortDelete(req: Request, res: Response) {
- Tele.emit('evt', { evt_type: 'sort:deleted' });
- const sort = await Sort.delete(req.params.sortId);
- res.json(sort);
-}
-
-const router = Router({ mergeParams: true });
-router.get(
- '/api/v1/db/meta/views/:viewId/sorts/',
- metaApiMetrics,
- ncMetaAclMw(sortList, 'sortList')
-);
-router.post(
- '/api/v1/db/meta/views/:viewId/sorts/',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/SortReq'),
- ncMetaAclMw(sortCreate, 'sortCreate')
-);
-router.get(
- '/api/v1/db/meta/sorts/:sortId',
- metaApiMetrics,
- ncMetaAclMw(sortGet, 'sortGet')
-);
-router.patch(
- '/api/v1/db/meta/sorts/:sortId',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/SortReq'),
- ncMetaAclMw(sortUpdate, 'sortUpdate')
-);
-router.delete(
- '/api/v1/db/meta/sorts/:sortId',
- metaApiMetrics,
- ncMetaAclMw(sortDelete, 'sortDelete')
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/swagger/swaggerApis.ts b/packages/nocodb/src/lib/meta/api/swagger/swaggerApis.ts
deleted file mode 100644
index c8cb637ba1..0000000000
--- a/packages/nocodb/src/lib/meta/api/swagger/swaggerApis.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-// @ts-ignore
-import catchError, { NcError } from '../../helpers/catchError';
-import { Router } from 'express';
-import Model from '../../../models/Model';
-import ncMetaAclMw from '../../helpers/ncMetaAclMw';
-import getSwaggerJSON from './helpers/getSwaggerJSON';
-import Project from '../../../models/Project';
-import getSwaggerHtml from './swaggerHtml';
-import getRedocHtml from './redocHtml';
-
-async function swaggerJson(req, res) {
- const project = await Project.get(req.params.projectId);
-
- if (!project) NcError.notFound();
-
- const models = await Model.list({
- project_id: req.params.projectId,
- base_id: null,
- });
-
- const swagger = await getSwaggerJSON(project, models);
-
- swagger.servers = [
- {
- url: req.ncSiteUrl,
- },
- {
- url: '{customUrl}',
- variables: {
- customUrl: {
- default: req.ncSiteUrl,
- description: 'Provide custom nocodb app base url',
- },
- },
- },
- ] as any;
-
- res.json(swagger);
-}
-
-function swaggerHtml(_, res) {
- res.send(getSwaggerHtml({ ncSiteUrl: process.env.NC_PUBLIC_URL || '' }));
-}
-
-function redocHtml(_, res) {
- res.send(getRedocHtml({ ncSiteUrl: process.env.NC_PUBLIC_URL || '' }));
-}
-
-const router = Router({ mergeParams: true });
-
-// todo: auth
-router.get(
- '/api/v1/db/meta/projects/:projectId/swagger.json',
- ncMetaAclMw(swaggerJson, 'swaggerJson')
-);
-
-router.get('/api/v1/db/meta/projects/:projectId/swagger', swaggerHtml);
-
-router.get('/api/v1/db/meta/projects/:projectId/redoc', redocHtml);
-
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/tableApis.ts b/packages/nocodb/src/lib/meta/api/tableApis.ts
deleted file mode 100644
index 4c5d6856a5..0000000000
--- a/packages/nocodb/src/lib/meta/api/tableApis.ts
+++ /dev/null
@@ -1,447 +0,0 @@
-import { Request, Response, Router } from 'express';
-import Model from '../../models/Model';
-import { Tele } from 'nc-help';
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-import DOMPurify from 'isomorphic-dompurify';
-import {
- AuditOperationSubTypes,
- AuditOperationTypes,
- isVirtualCol,
- ModelTypes,
- NormalColumnRequestType,
- TableListType,
- TableReqType,
- TableType,
- UITypes,
-} from 'nocodb-sdk';
-import ProjectMgrv2 from '../../db/sql-mgr/v2/ProjectMgrv2';
-import Project from '../../models/Project';
-import Audit from '../../models/Audit';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { getAjvValidatorMw } from './helpers';
-import { xcVisibilityMetaGet } from './modelVisibilityApis';
-import View from '../../models/View';
-import getColumnPropsFromUIDT from '../helpers/getColumnPropsFromUIDT';
-import mapDefaultDisplayValue from '../helpers/mapDefaultDisplayValue';
-import { NcError } from '../helpers/catchError';
-import getTableNameAlias, { getColumnNameAlias } from '../helpers/getTableName';
-import Column from '../../models/Column';
-import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
-import getColumnUiType from '../helpers/getColumnUiType';
-import LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-
-export async function tableGet(req: Request, res: Response) {
- const table = await Model.getWithInfo({
- id: req.params.tableId,
- });
-
- // todo: optimise
- const viewList = await xcVisibilityMetaGet(table.project_id, [table]);
-
- //await View.list(req.params.tableId)
- table.views = viewList.filter((table: any) => {
- return Object.keys((req as any).session?.passport?.user?.roles).some(
- (role) =>
- (req as any)?.session?.passport?.user?.roles[role] &&
- !table.disabled[role]
- );
- });
-
- res.json(table);
-}
-
-export async function tableReorder(req: Request, res: Response) {
- res.json(Model.updateOrder(req.params.tableId, req.body.order));
-}
-
-export async function tableList(req: Request, res: Response) {
- const viewList = await xcVisibilityMetaGet(req.params.projectId);
-
- // todo: optimise
- const tableViewMapping = viewList.reduce((o, view: any) => {
- o[view.fk_model_id] = o[view.fk_model_id] || 0;
- if (
- Object.keys((req as any).session?.passport?.user?.roles).some(
- (role) =>
- (req as any)?.session?.passport?.user?.roles[role] &&
- !view.disabled[role]
- )
- ) {
- o[view.fk_model_id]++;
- }
- return o;
- }, {});
-
- const tableList = (
- await Model.list({
- project_id: req.params.projectId,
- base_id: req.params.baseId,
- })
- ).filter((t) => tableViewMapping[t.id]);
-
- res.json(
- new PagedResponseImpl(
- req.query?.includeM2M === 'true'
- ? tableList
- : (tableList.filter((t) => !t.mm) as Model[])
- )
- );
-}
-
-export async function tableCreate(req: Request, res) {
- const project = await Project.getWithInfo(req.params.projectId);
- let base = project.bases[0];
-
- if (req.params.baseId) {
- base = project.bases.find((b) => b.id === req.params.baseId);
- }
-
- if (
- !req.body.table_name ||
- (project.prefix && project.prefix === req.body.table_name)
- ) {
- NcError.badRequest(
- 'Missing table name `table_name` property in request body'
- );
- }
-
- if (base.is_meta && project.prefix) {
- if (!req.body.table_name.startsWith(project.prefix)) {
- req.body.table_name = `${project.prefix}_${req.body.table_name}`;
- }
- }
-
- req.body.table_name = DOMPurify.sanitize(req.body.table_name);
-
- // validate table name
- if (/^\s+|\s+$/.test(req.body.table_name)) {
- NcError.badRequest(
- 'Leading or trailing whitespace not allowed in table names'
- );
- }
-
- if (
- !(await Model.checkTitleAvailable({
- table_name: req.body.table_name,
- project_id: project.id,
- base_id: base.id,
- }))
- ) {
- NcError.badRequest('Duplicate table name');
- }
-
- if (!req.body.title) {
- req.body.title = getTableNameAlias(
- req.body.table_name,
- project.prefix,
- base
- );
- }
-
- if (
- !(await Model.checkAliasAvailable({
- title: req.body.title,
- project_id: project.id,
- base_id: base.id,
- }))
- ) {
- NcError.badRequest('Duplicate table alias');
- }
-
- const sqlMgr = await ProjectMgrv2.getSqlMgr(project);
-
- const sqlClient = await NcConnectionMgrv2.getSqlClient(base);
-
- let tableNameLengthLimit = 255;
- const sqlClientType = sqlClient.knex.clientType();
- if (sqlClientType === 'mysql2' || sqlClientType === 'mysql') {
- tableNameLengthLimit = 64;
- } else if (sqlClientType === 'pg') {
- tableNameLengthLimit = 63;
- } else if (sqlClientType === 'mssql') {
- tableNameLengthLimit = 128;
- }
-
- if (req.body.table_name.length > tableNameLengthLimit) {
- NcError.badRequest(`Table name exceeds ${tableNameLengthLimit} characters`);
- }
-
- const mxColumnLength = Column.getMaxColumnNameLength(sqlClientType);
-
- for (const column of req.body.columns) {
- if (column.column_name.length > mxColumnLength) {
- NcError.badRequest(
- `Column name ${column.column_name} exceeds ${mxColumnLength} characters`
- );
- }
- }
-
- req.body.columns = req.body.columns?.map((c) => ({
- ...getColumnPropsFromUIDT(c as any, base),
- cn: c.column_name,
- }));
- await sqlMgr.sqlOpPlus(base, 'tableCreate', {
- ...req.body,
- tn: req.body.table_name,
- });
-
- const columns: Array<
- Omit & {
- cn: string;
- system?: boolean;
- }
- > = (await sqlClient.columnList({ tn: req.body.table_name }))?.data?.list;
-
- const tables = await Model.list({
- project_id: project.id,
- base_id: base.id,
- });
-
- await Audit.insert({
- project_id: project.id,
- base_id: base.id,
- op_type: AuditOperationTypes.TABLE,
- op_sub_type: AuditOperationSubTypes.CREATED,
- user: (req as any)?.user?.email,
- description: `created table ${req.body.table_name} with alias ${req.body.title} `,
- ip: (req as any).clientIp,
- }).then(() => {});
-
- mapDefaultDisplayValue(req.body.columns);
-
- Tele.emit('evt', { evt_type: 'table:created' });
-
- res.json(
- await Model.insert(project.id, base.id, {
- ...req.body,
- columns: columns.map((c, i) => {
- const colMetaFromReq = req.body?.columns?.find(
- (c1) => c.cn === c1.column_name
- ) as NormalColumnRequestType;
- return {
- ...colMetaFromReq,
- uidt:
- (colMetaFromReq?.uidt as string) ||
- c.uidt ||
- getColumnUiType(base, c),
- ...c,
- dtxp: [UITypes.MultiSelect, UITypes.SingleSelect].includes(
- colMetaFromReq.uidt as any
- )
- ? colMetaFromReq.dtxp
- : c.dtxp,
- title: colMetaFromReq?.title || getColumnNameAlias(c.cn, base),
- column_name: c.cn,
- order: i + 1,
- } as NormalColumnRequestType;
- }),
- order: +(tables?.pop()?.order ?? 0) + 1,
- })
- );
-}
-
-export async function tableUpdate(req: Request, res) {
- const model = await Model.get(req.params.tableId);
-
- const project = await Project.getWithInfo(
- req.body.project_id || (req as any).ncProjectId
- );
- const base = project.bases.find((b) => b.id === model.base_id);
-
- if (model.project_id !== project.id) {
- NcError.badRequest('Model does not belong to project');
- }
-
- // if meta present update meta and return
- // todo: allow user to update meta and other prop in single api call
- if ('meta' in req.body) {
- await Model.updateMeta(req.params.tableId, req.body.meta);
-
- return res.json({ msg: 'success' });
- }
-
- if (!req.body.table_name) {
- NcError.badRequest(
- 'Missing table name `table_name` property in request body'
- );
- }
-
- if (base.is_meta && project.prefix) {
- if (!req.body.table_name.startsWith(project.prefix)) {
- req.body.table_name = `${project.prefix}${req.body.table_name}`;
- }
- }
-
- req.body.table_name = DOMPurify.sanitize(req.body.table_name);
-
- // validate table name
- if (/^\s+|\s+$/.test(req.body.table_name)) {
- NcError.badRequest(
- 'Leading or trailing whitespace not allowed in table names'
- );
- }
-
- if (
- !(await Model.checkTitleAvailable({
- table_name: req.body.table_name,
- project_id: project.id,
- base_id: base.id,
- }))
- ) {
- NcError.badRequest('Duplicate table name');
- }
-
- if (!req.body.title) {
- req.body.title = getTableNameAlias(
- req.body.table_name,
- project.prefix,
- base
- );
- }
-
- if (
- !(await Model.checkAliasAvailable({
- title: req.body.title,
- project_id: project.id,
- base_id: base.id,
- }))
- ) {
- NcError.badRequest('Duplicate table alias');
- }
-
- const sqlMgr = await ProjectMgrv2.getSqlMgr(project);
- const sqlClient = await NcConnectionMgrv2.getSqlClient(base);
-
- let tableNameLengthLimit = 255;
- const sqlClientType = sqlClient.knex.clientType();
- if (sqlClientType === 'mysql2' || sqlClientType === 'mysql') {
- tableNameLengthLimit = 64;
- } else if (sqlClientType === 'pg') {
- tableNameLengthLimit = 63;
- } else if (sqlClientType === 'mssql') {
- tableNameLengthLimit = 128;
- }
-
- if (req.body.table_name.length > tableNameLengthLimit) {
- NcError.badRequest(`Table name exceeds ${tableNameLengthLimit} characters`);
- }
-
- await Model.updateAliasAndTableName(
- req.params.tableId,
- req.body.title,
- req.body.table_name
- );
-
- await sqlMgr.sqlOpPlus(base, 'tableRename', {
- ...req.body,
- tn: req.body.table_name,
- tn_old: model.table_name,
- });
-
- Tele.emit('evt', { evt_type: 'table:updated' });
-
- res.json({ msg: 'success' });
-}
-
-export async function tableDelete(req: Request, res: Response) {
- const table = await Model.getByIdOrName({ id: req.params.tableId });
- await table.getColumns();
-
- const relationColumns = table.columns.filter(
- (c) => c.uidt === UITypes.LinkToAnotherRecord
- );
-
- if (relationColumns?.length) {
- const referredTables = await Promise.all(
- relationColumns.map(async (c) =>
- c
- .getColOptions()
- .then((opt) => opt.getRelatedTable())
- .then()
- )
- );
- NcError.badRequest(
- `Table can't be deleted since Table is being referred in following tables : ${referredTables.join(
- ', '
- )}. Delete LinkToAnotherRecord columns and try again.`
- );
- }
-
- const project = await Project.getWithInfo(table.project_id);
- const base = project.bases.find((b) => b.id === table.base_id);
- const sqlMgr = await ProjectMgrv2.getSqlMgr(project);
- (table as any).tn = table.table_name;
- table.columns = table.columns.filter((c) => !isVirtualCol(c));
- table.columns.forEach((c) => {
- (c as any).cn = c.column_name;
- });
-
- if (table.type === ModelTypes.TABLE) {
- await sqlMgr.sqlOpPlus(base, 'tableDelete', table);
- } else if (table.type === ModelTypes.VIEW) {
- await sqlMgr.sqlOpPlus(base, 'viewDelete', {
- ...table,
- view_name: table.table_name,
- });
- }
-
- await Audit.insert({
- project_id: project.id,
- base_id: base.id,
- op_type: AuditOperationTypes.TABLE,
- op_sub_type: AuditOperationSubTypes.DELETED,
- user: (req as any)?.user?.email,
- description: `Deleted ${table.type} ${table.table_name} with alias ${table.title} `,
- ip: (req as any).clientIp,
- }).then(() => {});
-
- Tele.emit('evt', { evt_type: 'table:deleted' });
-
- res.json(await table.delete());
-}
-
-const router = Router({ mergeParams: true });
-router.get(
- '/api/v1/db/meta/projects/:projectId/tables',
- metaApiMetrics,
- ncMetaAclMw(tableList, 'tableList')
-);
-router.get(
- '/api/v1/db/meta/projects/:projectId/:baseId/tables',
- metaApiMetrics,
- ncMetaAclMw(tableList, 'tableList')
-);
-router.post(
- '/api/v1/db/meta/projects/:projectId/tables',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/TableReq'),
- ncMetaAclMw(tableCreate, 'tableCreate')
-);
-router.post(
- '/api/v1/db/meta/projects/:projectId/:baseId/tables',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/TableReq'),
- ncMetaAclMw(tableCreate, 'tableCreate')
-);
-router.get(
- '/api/v1/db/meta/tables/:tableId',
- metaApiMetrics,
- ncMetaAclMw(tableGet, 'tableGet')
-);
-router.patch(
- '/api/v1/db/meta/tables/:tableId',
- metaApiMetrics,
- ncMetaAclMw(tableUpdate, 'tableUpdate')
-);
-router.delete(
- '/api/v1/db/meta/tables/:tableId',
- metaApiMetrics,
- ncMetaAclMw(tableDelete, 'tableDelete')
-);
-router.post(
- '/api/v1/db/meta/tables/:tableId/reorder',
- metaApiMetrics,
- ncMetaAclMw(tableReorder, 'tableReorder')
-);
-export default router;
diff --git a/packages/nocodb/src/lib/meta/api/userApi/index.ts b/packages/nocodb/src/lib/meta/api/userApi/index.ts
deleted file mode 100644
index c4a5003430..0000000000
--- a/packages/nocodb/src/lib/meta/api/userApi/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './userApis';
diff --git a/packages/nocodb/src/lib/meta/api/viewApis.ts b/packages/nocodb/src/lib/meta/api/viewApis.ts
deleted file mode 100644
index 6078e43fae..0000000000
--- a/packages/nocodb/src/lib/meta/api/viewApis.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-import { Request, Response, Router } from 'express';
-// @ts-ignore
-import Model from '../../models/Model';
-import { Tele } from 'nc-help';
-// @ts-ignore
-import { PagedResponseImpl } from '../helpers/PagedResponse';
-// @ts-ignore
-import { Table, TableReq, ViewList } from 'nocodb-sdk';
-// @ts-ignore
-import ProjectMgrv2 from '../../db/sql-mgr/v2/ProjectMgrv2';
-// @ts-ignore
-import Project from '../../models/Project';
-import View from '../../models/View';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { xcVisibilityMetaGet } from './modelVisibilityApis';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import { getAjvValidatorMw } from './helpers';
-// @ts-ignore
-export async function viewGet(req: Request, res: Response) {}
-
-// @ts-ignore
-export async function viewList(
- req: Request,
- res: Response
-) {
- const model = await Model.get(req.params.tableId);
-
- const viewList = await xcVisibilityMetaGet(
- // req.params.projectId,
- // req.params.baseId,
- model.project_id,
- [model]
- );
-
- //await View.list(req.params.tableId)
- const filteredViewList = viewList.filter((view: any) => {
- return Object.keys((req as any).session?.passport?.user?.roles).some(
- (role) =>
- (req as any)?.session?.passport?.user?.roles[role] &&
- !view.disabled[role]
- );
- });
-
- res.json(new PagedResponseImpl(filteredViewList));
-}
-
-// @ts-ignore
-export async function shareView(
- req: Request,
- res: Response
-) {
- Tele.emit('evt', { evt_type: 'sharedView:generated-link' });
- res.json(await View.share(req.params.viewId));
-}
-
-// @ts-ignore
-export async function viewCreate(req: Request, res, next) {}
-
-// @ts-ignore
-export async function viewUpdate(req, res) {
- const result = await View.update(req.params.viewId, req.body);
- Tele.emit('evt', { evt_type: 'vtable:updated', show_as: result.type });
- res.json(result);
-}
-
-// @ts-ignore
-export async function viewDelete(req: Request, res: Response, next) {
- const result = await View.delete(req.params.viewId);
- Tele.emit('evt', { evt_type: 'vtable:deleted' });
- res.json(result);
-}
-
-async function shareViewUpdate(req: Request, res) {
- Tele.emit('evt', { evt_type: 'sharedView:updated' });
- res.json(await View.update(req.params.viewId, req.body));
-}
-
-async function shareViewDelete(req: Request, res) {
- Tele.emit('evt', { evt_type: 'sharedView:deleted' });
- res.json(await View.sharedViewDelete(req.params.viewId));
-}
-
-async function showAllColumns(req: Request, res) {
- res.json(
- await View.showAllColumns(
- req.params.viewId,
- (req.query?.ignoreIds || [])
- )
- );
-}
-
-async function hideAllColumns(req: Request, res) {
- res.json(
- await View.hideAllColumns(
- req.params.viewId,
- (req.query?.ignoreIds || [])
- )
- );
-}
-
-async function shareViewList(req: Request, res) {
- res.json(await View.shareViewList(req.params.tableId));
-}
-
-const router = Router({ mergeParams: true });
-router.get(
- '/api/v1/db/meta/tables/:tableId/views',
- metaApiMetrics,
- ncMetaAclMw(viewList, 'viewList')
-);
-router.patch(
- '/api/v1/db/meta/views/:viewId',
- getAjvValidatorMw('swagger.json#/components/schemas/ViewReq'),
- metaApiMetrics,
- ncMetaAclMw(viewUpdate, 'viewUpdate')
-);
-router.delete(
- '/api/v1/db/meta/views/:viewId',
- metaApiMetrics,
- ncMetaAclMw(viewDelete, 'viewDelete')
-);
-router.post(
- '/api/v1/db/meta/views/:viewId/show-all',
- metaApiMetrics,
- ncMetaAclMw(showAllColumns, 'showAllColumns')
-);
-router.post(
- '/api/v1/db/meta/views/:viewId/hide-all',
- metaApiMetrics,
- ncMetaAclMw(hideAllColumns, 'hideAllColumns')
-);
-
-router.get(
- '/api/v1/db/meta/tables/:tableId/share',
- metaApiMetrics,
- ncMetaAclMw(shareViewList, 'shareViewList')
-);
-router.post(
- '/api/v1/db/meta/views/:viewId/share',
- ncMetaAclMw(shareView, 'shareView')
-);
-router.patch(
- '/api/v1/db/meta/views/:viewId/share',
- getAjvValidatorMw('swagger.json#/components/schemas/SharedViewReq'),
- metaApiMetrics,
- ncMetaAclMw(shareViewUpdate, 'shareViewUpdate')
-);
-router.delete(
- '/api/v1/db/meta/views/:viewId/share',
- metaApiMetrics,
- ncMetaAclMw(shareViewDelete, 'shareViewDelete')
-);
-
-export default router;
diff --git a/packages/nocodb/src/lib/meta/helpers/apiMetrics.ts b/packages/nocodb/src/lib/meta/helpers/apiMetrics.ts
index 82c6a0c32b..e276c99ef9 100644
--- a/packages/nocodb/src/lib/meta/helpers/apiMetrics.ts
+++ b/packages/nocodb/src/lib/meta/helpers/apiMetrics.ts
@@ -1,5 +1,5 @@
import { Request } from 'express';
-import { Tele } from 'nc-help';
+import { T } from 'nc-help';
const countMap = {};
@@ -9,7 +9,7 @@ const metrics = async (req: Request, c = 150) => {
const event = `a:api:${req.route.path}:${req.method}`;
countMap[event] = (countMap[event] || 0) + 1;
if (countMap[event] >= c) {
- Tele.event({ event });
+ T.event({ event });
countMap[event] = 0;
}
};
diff --git a/packages/nocodb/src/lib/meta/helpers/catchError.ts b/packages/nocodb/src/lib/meta/helpers/catchError.ts
index 1c3393ed0d..4c6da5d46d 100644
--- a/packages/nocodb/src/lib/meta/helpers/catchError.ts
+++ b/packages/nocodb/src/lib/meta/helpers/catchError.ts
@@ -1,3 +1,5 @@
+import { ErrorObject } from 'ajv';
+
enum DBError {
TABLE_EXIST = 'TABLE_EXIST',
TABLE_NOT_EXIST = 'TABLE_NOT_EXIST',
@@ -5,6 +7,7 @@ enum DBError {
COLUMN_NOT_EXIST = 'COLUMN_NOT_EXIST',
CONSTRAINT_EXIST = 'CONSTRAINT_EXIST',
CONSTRAINT_NOT_EXIST = 'CONSTRAINT_NOT_EXIST',
+ COLUMN_NOT_NULL = 'COLUMN_NOT_NULL',
}
// extract db errors using database error code
@@ -20,6 +23,7 @@ function extractDBError(error): {
let extra: Record;
let type: DBError;
+ // todo: handle not null constraint error for all databases
switch (error.code) {
// sqlite errors
case 'SQLITE_BUSY':
@@ -172,6 +176,19 @@ function extractDBError(error): {
break;
case 'ER_BAD_NULL_ERROR':
message = 'A null value is not allowed for this field.';
+ {
+ const extractColNameMatch = error.message.match(
+ /Column '(\w+)' cannot be null/i
+ );
+ if (extractColNameMatch && extractColNameMatch[1]) {
+ message = `The column '${extractColNameMatch[1]}' cannot be null.`;
+ type = DBError.COLUMN_NOT_NULL;
+ extra = {
+ column: extractColNameMatch[1],
+ };
+ }
+ }
+
break;
case 'ER_DATA_TOO_LONG':
message = 'The data entered is too long for this field.';
@@ -394,6 +411,8 @@ export default function (
return res.status(500).json({ msg: e.message });
} else if (e instanceof NotImplemented) {
return res.status(501).json({ msg: e.message });
+ } else if (e instanceof AjvError) {
+ return res.status(400).json({ msg: e.message, errors: e.errors });
}
next(e);
}
@@ -412,6 +431,15 @@ class InternalServerError extends Error {}
class NotImplemented extends Error {}
+class AjvError extends Error {
+ constructor(param: { message: string; errors: ErrorObject[] }) {
+ super(param.message);
+ this.errors = param.errors;
+ }
+
+ errors: ErrorObject[];
+}
+
export class NcError {
static notFound(message = 'Not found') {
throw new NotFound(message);
@@ -436,4 +464,8 @@ export class NcError {
static notImplemented(message = 'Not implemented') {
throw new NotImplemented(message);
}
+
+ static ajvValidationError(param: { message: string; errors: ErrorObject[] }) {
+ throw new AjvError(param);
+ }
}
diff --git a/packages/nocodb/src/lib/meta/helpers/getHandler.ts b/packages/nocodb/src/lib/meta/helpers/getHandler.ts
index 1f40f45b2f..07c57a08eb 100644
--- a/packages/nocodb/src/lib/meta/helpers/getHandler.ts
+++ b/packages/nocodb/src/lib/meta/helpers/getHandler.ts
@@ -12,3 +12,20 @@ export default function getHandler(
return eeHandler(...args);
};
}
+
+export function getConditionalHandler<
+ T extends (...args: any[]) => any,
+ U extends (...args: any[]) => any
+>(
+ defaultHandler: T,
+ eeHandler: U
+): (
+ ...args: Parameters | Parameters
+) => Promise | ReturnType> {
+ return async (...args: Parameters | Parameters) => {
+ if (Noco.isEE()) {
+ return defaultHandler(...args);
+ }
+ return eeHandler(...args);
+ };
+}
diff --git a/packages/nocodb/src/lib/models/FormView.ts b/packages/nocodb/src/lib/models/FormView.ts
index 8ec5047d5d..9dbe572f88 100644
--- a/packages/nocodb/src/lib/models/FormView.ts
+++ b/packages/nocodb/src/lib/models/FormView.ts
@@ -1,6 +1,6 @@
import Noco from '../Noco';
import { CacheGetType, CacheScope, MetaTable } from '../utils/globals';
-import { FormType } from 'nocodb-sdk';
+import { BoolType, FormType } from 'nocodb-sdk';
import { deserializeJSON, serializeJSON } from '../utils/serialize';
import FormViewColumn from './FormViewColumn';
import View from './View';
@@ -8,6 +8,9 @@ import NocoCache from '../cache/NocoCache';
import { extractProps } from '../meta/helpers/extractProps';
export default class FormView implements FormType {
+ show: BoolType;
+ is_default: BoolType;
+ order: number;
title?: string;
heading?: string;
subheading?: string;
@@ -17,8 +20,8 @@ export default class FormView implements FormType {
email?: string;
banner_image_url?: string;
logo_url?: string;
- submit_another_form?: boolean;
- show_blank_form?: boolean;
+ submit_another_form?: BoolType;
+ show_blank_form?: BoolType;
fk_view_id: string;
columns?: FormViewColumn[];
diff --git a/packages/nocodb/src/lib/models/GalleryView.ts b/packages/nocodb/src/lib/models/GalleryView.ts
index 2cf2f6ce8f..8f530d2154 100644
--- a/packages/nocodb/src/lib/models/GalleryView.ts
+++ b/packages/nocodb/src/lib/models/GalleryView.ts
@@ -1,24 +1,24 @@
import Noco from '../Noco';
import { CacheGetType, CacheScope, MetaTable } from '../utils/globals';
-import { GalleryColumnType, GalleryType, UITypes } from 'nocodb-sdk';
+import { BoolType, GalleryColumnType, GalleryType, UITypes } from 'nocodb-sdk';
import View from './View';
import NocoCache from '../cache/NocoCache';
import { extractProps } from '../meta/helpers/extractProps';
export default class GalleryView implements GalleryType {
fk_view_id?: string;
- deleted?: boolean;
+ deleted?: BoolType;
order?: number;
- next_enabled?: boolean;
- prev_enabled?: boolean;
+ next_enabled?: BoolType;
+ prev_enabled?: BoolType;
cover_image_idx?: number;
cover_image?: string;
restrict_types?: string;
restrict_size?: string;
restrict_number?: string;
- public?: boolean;
+ public?: BoolType;
password?: string;
- show_all_fields?: boolean;
+ show_all_fields?: BoolType;
fk_cover_image_col_id?: string;
project_id?: string;
diff --git a/packages/nocodb/src/lib/models/Hook.ts b/packages/nocodb/src/lib/models/Hook.ts
index 031521926e..bf1c64611b 100644
--- a/packages/nocodb/src/lib/models/Hook.ts
+++ b/packages/nocodb/src/lib/models/Hook.ts
@@ -1,4 +1,4 @@
-import { HookType } from 'nocodb-sdk';
+import { BoolType, HookReqType, HookType } from 'nocodb-sdk';
import {
CacheDelDirection,
CacheGetType,
@@ -21,21 +21,21 @@ export default class Hook implements HookType {
type?: string;
event?: 'after' | 'before';
operation?: 'insert' | 'delete' | 'update';
- async?: boolean;
+ async?: BoolType;
payload?: string;
url?: string;
headers?: string;
- condition?: boolean;
+ condition?: BoolType;
notification?: string | object;
retries?: number;
retry_interval?: number;
timeout?: number;
- active?: boolean;
+ active?: BoolType;
project_id?: string;
base_id?: string;
- constructor(hook: Partial) {
+ constructor(hook: Partial) {
Object.assign(this, hook);
}
diff --git a/packages/nocodb/src/lib/models/KanbanView.ts b/packages/nocodb/src/lib/models/KanbanView.ts
index 6ac1b65d09..9bff3fbed4 100644
--- a/packages/nocodb/src/lib/models/KanbanView.ts
+++ b/packages/nocodb/src/lib/models/KanbanView.ts
@@ -1,5 +1,5 @@
import Noco from '../Noco';
-import { KanbanType, UITypes } from 'nocodb-sdk';
+import { BoolType, KanbanType, UITypes } from 'nocodb-sdk';
import { CacheGetType, CacheScope, MetaTable } from '../utils/globals';
import View from './View';
import NocoCache from '../cache/NocoCache';
@@ -16,12 +16,12 @@ export default class KanbanView implements KanbanType {
// below fields are not in use at this moment
// keep them for time being
- show?: boolean;
+ show?: BoolType;
order?: number;
uuid?: string;
- public?: boolean;
+ public?: BoolType;
password?: string;
- show_all_fields?: boolean;
+ show_all_fields?: BoolType;
constructor(data: KanbanView) {
Object.assign(this, data);
diff --git a/packages/nocodb/src/lib/models/Model.ts b/packages/nocodb/src/lib/models/Model.ts
index 5fdf81219c..b90517b84c 100644
--- a/packages/nocodb/src/lib/models/Model.ts
+++ b/packages/nocodb/src/lib/models/Model.ts
@@ -27,7 +27,6 @@ import { extractProps } from '../meta/helpers/extractProps';
export default class Model implements TableType {
copy_enabled: BoolType;
- created_at: Date | number | string;
base_id: 'db' | string;
deleted: BoolType;
enabled: BoolType;
@@ -42,7 +41,6 @@ export default class Model implements TableType {
show_all_fields: boolean;
tags: string;
type: ModelTypes;
- updated_at: Date | number | string;
table_name: string;
title: string;
diff --git a/packages/nocodb/src/lib/models/ProjectUser.ts b/packages/nocodb/src/lib/models/ProjectUser.ts
index 8b25ff6af1..14e208cb60 100644
--- a/packages/nocodb/src/lib/models/ProjectUser.ts
+++ b/packages/nocodb/src/lib/models/ProjectUser.ts
@@ -217,7 +217,7 @@ export default class ProjectUser {
static async getProjectsList(
userId: string,
- _params: any,
+ _params?: any,
ncMeta = Noco.ncMeta
): Promise {
// todo: pagination
diff --git a/packages/nocodb/src/lib/models/View.ts b/packages/nocodb/src/lib/models/View.ts
index 41e17d6d67..e453da37ea 100644
--- a/packages/nocodb/src/lib/models/View.ts
+++ b/packages/nocodb/src/lib/models/View.ts
@@ -15,6 +15,7 @@ import GridViewColumn from './GridViewColumn';
import Sort from './Sort';
import Filter from './Filter';
import {
+ BoolType,
ColumnReqType,
isSystemColumn,
UITypes,
@@ -942,7 +943,7 @@ export default class View implements ViewType {
body: {
title?: string;
order?: number;
- show_system_fields?: boolean;
+ show_system_fields?: BoolType;
lock_type?: string;
password?: string;
uuid?: string;
diff --git a/packages/nocodb/src/lib/models/index.ts b/packages/nocodb/src/lib/models/index.ts
new file mode 100644
index 0000000000..534c8ce025
--- /dev/null
+++ b/packages/nocodb/src/lib/models/index.ts
@@ -0,0 +1,36 @@
+export { default as ApiToken } from './ApiToken';
+export { default as Audit } from './Audit';
+export { default as BarcodeColumn } from './BarcodeColumn';
+export { default as Base } from './Base';
+export { default as Column } from './Column';
+export { default as Filter } from './Filter';
+export { default as FormulaColumn } from './FormulaColumn';
+export { default as FormView } from './FormView';
+export { default as FormViewColumn } from './FormViewColumn';
+export { default as GalleryView } from './GalleryView';
+export { default as GalleryViewColumn } from './GalleryViewColumn';
+export { default as GridView } from './GridView';
+export { default as GridViewColumn } from './GridViewColumn';
+export { default as Hook } from './Hook';
+export { default as HookFilter } from './HookFilter';
+export { default as HookLog } from './HookLog';
+export { default as KanbanView } from './KanbanView';
+export { default as KanbanViewColumn } from './KanbanViewColumn';
+export { default as LinkToAnotherRecordColumn } from './LinkToAnotherRecordColumn';
+export { default as LookupColumn } from './LookupColumn';
+export { default as MapView } from './MapView';
+export { default as MapViewColumn } from './MapViewColumn';
+export { default as Model } from './Model';
+export { default as ModelRoleVisibility } from './ModelRoleVisibility';
+export { default as Plugin } from './Plugin';
+export { default as Project } from './Project';
+export { default as ProjectUser } from './ProjectUser';
+export { default as QrCodeColumn } from './QrCodeColumn';
+export { default as RollupColumn } from './RollupColumn';
+export { default as SelectOption } from './SelectOption';
+export { default as Sort } from './Sort';
+export { default as Store } from './Store';
+export { default as SyncLogs } from './SyncLogs';
+export { default as SyncSource } from './SyncSource';
+export { default as User } from './User';
+export { default as View } from './View';
diff --git a/packages/nocodb/src/lib/services/apiTokenService.ts b/packages/nocodb/src/lib/services/apiTokenService.ts
new file mode 100644
index 0000000000..329bcb305c
--- /dev/null
+++ b/packages/nocodb/src/lib/services/apiTokenService.ts
@@ -0,0 +1,36 @@
+import { ApiTokenReqType, OrgUserRoles } from 'nocodb-sdk';
+import { T } from 'nc-help';
+import { validatePayload } from '../meta/api/helpers';
+import { NcError } from '../meta/helpers/catchError';
+import ApiToken from '../models/ApiToken';
+import User from '../models/User';
+
+export async function apiTokenList(param: { userId: string }) {
+ return ApiToken.list(param.userId);
+}
+export async function apiTokenCreate(param: {
+ userId: string;
+ tokenBody: ApiTokenReqType;
+}) {
+ await validatePayload(
+ 'swagger.json#/components/schemas/ApiTokenReq',
+ param.tokenBody
+ );
+
+ T.emit('evt', { evt_type: 'apiToken:created' });
+ return ApiToken.insert({ ...param.tokenBody, fk_user_id: param.userId });
+}
+
+export async function apiTokenDelete(param: { token; user: User }) {
+ const apiToken = await ApiToken.getByToken(param.token);
+ if (
+ !param.user.roles.includes(OrgUserRoles.SUPER_ADMIN) &&
+ apiToken.fk_user_id !== param.user.id
+ ) {
+ NcError.notFound('Token not found');
+ }
+ T.emit('evt', { evt_type: 'apiToken:deleted' });
+
+ // todo: verify token belongs to the user
+ return await ApiToken.delete(param.token);
+}
diff --git a/packages/nocodb/src/lib/services/attachmentService.ts b/packages/nocodb/src/lib/services/attachmentService.ts
new file mode 100644
index 0000000000..d65134e75b
--- /dev/null
+++ b/packages/nocodb/src/lib/services/attachmentService.ts
@@ -0,0 +1,116 @@
+import { nanoid } from 'nanoid';
+import path from 'path';
+import slash from 'slash';
+import mimetypes, { mimeIcons } from '../utils/mimeTypes';
+import { T } from 'nc-help';
+import NcPluginMgrv2 from '../meta/helpers/NcPluginMgrv2';
+import Local from '../v1-legacy/plugins/adapters/storage/Local';
+
+export async function upload(param: {
+ path?: string;
+ // todo: proper type
+ files: unknown[];
+}) {
+ const filePath = sanitizeUrlPath(param.path?.toString()?.split('/') || ['']);
+ const destPath = path.join('nc', 'uploads', ...filePath);
+
+ const storageAdapter = await NcPluginMgrv2.storageAdapter();
+
+ const attachments = await Promise.all(
+ param.files?.map(async (file: any) => {
+ const fileName = `${nanoid(18)}${path.extname(file.originalname)}`;
+
+ const url = await storageAdapter.fileCreate(
+ slash(path.join(destPath, fileName)),
+ file
+ );
+
+ let attachmentPath;
+
+ // if `url` is null, then it is local attachment
+ if (!url) {
+ // then store the attachement path only
+ // url will be constructued in `useAttachmentCell`
+ attachmentPath = `download/${filePath.join('/')}/${fileName}`;
+ }
+
+ return {
+ ...(url ? { url } : {}),
+ ...(attachmentPath ? { path: attachmentPath } : {}),
+ title: file.originalname,
+ mimetype: file.mimetype,
+ size: file.size,
+ icon: mimeIcons[path.extname(file.originalname).slice(1)] || undefined,
+ };
+ })
+ );
+
+ T.emit('evt', { evt_type: 'image:uploaded' });
+
+ return attachments;
+}
+
+export async function uploadViaURL(param: {
+ path?: string;
+ urls: {
+ url: string;
+ fileName: string;
+ mimetype?: string;
+ size?: string | number;
+ }[];
+}) {
+ const filePath = sanitizeUrlPath(param?.path?.toString()?.split('/') || ['']);
+ const destPath = path.join('nc', 'uploads', ...filePath);
+
+ const storageAdapter = await NcPluginMgrv2.storageAdapter();
+
+ const attachments = await Promise.all(
+ param.urls?.map?.(async (urlMeta) => {
+ const { url, fileName: _fileName } = urlMeta;
+
+ const fileName = `${nanoid(18)}${_fileName || url.split('/').pop()}`;
+
+ const attachmentUrl = await (storageAdapter as any).fileCreateByUrl(
+ slash(path.join(destPath, fileName)),
+ url
+ );
+
+ let attachmentPath;
+
+ // if `attachmentUrl` is null, then it is local attachment
+ if (!attachmentUrl) {
+ // then store the attachement path only
+ // url will be constructued in `useAttachmentCell`
+ attachmentPath = `download/${filePath.join('/')}/${fileName}`;
+ }
+
+ return {
+ ...(attachmentUrl ? { url: attachmentUrl } : {}),
+ ...(attachmentPath ? { path: attachmentPath } : {}),
+ title: fileName,
+ mimetype: urlMeta.mimetype,
+ size: urlMeta.size,
+ icon: mimeIcons[path.extname(fileName).slice(1)] || undefined,
+ };
+ })
+ );
+
+ T.emit('evt', { evt_type: 'image:uploaded' });
+
+ return attachments;
+}
+
+export async function fileRead(param: { path: string }) {
+ // get the local storage adapter to display local attachments
+ const storageAdapter = new Local();
+ const type =
+ mimetypes[path.extname(param.path).split('/').pop().slice(1)] ||
+ 'text/plain';
+
+ const img = await storageAdapter.fileRead(slash(param.path));
+ return { img, type };
+}
+
+export function sanitizeUrlPath(paths) {
+ return paths.map((url) => url.replace(/[/.?#]+/g, '_'));
+}
diff --git a/packages/nocodb/src/lib/services/auditService.ts b/packages/nocodb/src/lib/services/auditService.ts
new file mode 100644
index 0000000000..886934cc84
--- /dev/null
+++ b/packages/nocodb/src/lib/services/auditService.ts
@@ -0,0 +1,78 @@
+import { validatePayload } from '../meta/api/helpers';
+import Audit from '../models/Audit';
+import {
+ AuditOperationSubTypes,
+ AuditOperationTypes,
+ AuditRowUpdateReqType,
+} from 'nocodb-sdk';
+import Model from '../models/Model';
+import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
+
+import DOMPurify from 'isomorphic-dompurify';
+
+export async function commentRow(param: {
+ rowId: string;
+ body: AuditRowUpdateReqType;
+ user: any;
+}) {
+ await validatePayload(
+ 'swagger.json#/components/schemas/CommentReq',
+ param.body
+ );
+
+ return await Audit.insert({
+ ...param.body,
+ user: param.user?.email,
+ op_type: AuditOperationTypes.COMMENT,
+ });
+}
+
+export async function auditRowUpdate(param: {
+ rowId: string;
+ body: AuditRowUpdateReqType;
+}) {
+ await validatePayload(
+ 'swagger.json#/components/schemas/AuditRowUpdateReq',
+ param.body
+ );
+
+ const model = await Model.getByIdOrName({ id: param.body.fk_model_id });
+ return await Audit.insert({
+ fk_model_id: param.body.fk_model_id,
+ row_id: param.rowId,
+ op_type: AuditOperationTypes.DATA,
+ op_sub_type: AuditOperationSubTypes.UPDATE,
+ description: DOMPurify.sanitize(
+ `Table ${model.table_name} : field ${param.body.column_name} got changed from ${param.body.prev_value} to ${param.body.value}`
+ ),
+ details: DOMPurify.sanitize(`${param.body.column_name}
+ : ${param.body.prev_value}
+ ${param.body.value}`),
+ ip: (param as any).clientIp,
+ user: (param as any).user?.email,
+ });
+}
+
+export async function commentList(param: { query: any }) {
+ return await Audit.commentsList(param.query);
+}
+
+export async function auditList(param: { query: any; projectId: string }) {
+ return new PagedResponseImpl(
+ await Audit.projectAuditList(param.projectId, param.query),
+ {
+ count: await Audit.projectAuditCount(param.projectId),
+ ...param.query,
+ }
+ );
+}
+
+export async function commentsCount(param: {
+ fk_model_id: string;
+ ids: string[];
+}) {
+ return await Audit.commentsCount({
+ fk_model_id: param.fk_model_id as string,
+ ids: param.ids as string[],
+ });
+}
diff --git a/packages/nocodb/src/lib/services/baseService.ts b/packages/nocodb/src/lib/services/baseService.ts
new file mode 100644
index 0000000000..21adb99ac8
--- /dev/null
+++ b/packages/nocodb/src/lib/services/baseService.ts
@@ -0,0 +1,82 @@
+import Project from '../models/Project';
+import { BaseReqType } from 'nocodb-sdk';
+import { syncBaseMigration } from '../meta/helpers/syncMigration';
+import Base from '../models/Base';
+import { T } from 'nc-help';
+import { populateMeta, validatePayload } from '../meta/api/helpers';
+
+export async function baseGetWithConfig(param: { baseId: any }) {
+ const base = await Base.get(param.baseId);
+
+ base.config = base.getConnectionConfig();
+
+ return base;
+}
+
+export async function baseUpdate(param: {
+ baseId: string;
+ base: BaseReqType;
+ projectId: string;
+}) {
+ validatePayload('swagger.json#/components/schemas/BaseReq', param.base);
+
+ const baseBody = param.base;
+ const project = await Project.getWithInfo(param.projectId);
+ const base = await Base.updateBase(param.baseId, {
+ ...baseBody,
+ type: baseBody.config?.client,
+ projectId: project.id,
+ id: param.baseId,
+ });
+
+ delete base.config;
+
+ T.emit('evt', {
+ evt_type: 'base:updated',
+ });
+
+ return base;
+}
+
+export async function baseList(param: { projectId: string }) {
+ const bases = await Base.list({ projectId: param.projectId });
+
+ return bases;
+}
+
+export async function baseDelete(param: { baseId: string }) {
+ const base = await Base.get(param.baseId);
+ await base.delete();
+ T.emit('evt', { evt_type: 'base:deleted' });
+ return true;
+}
+
+export async function baseCreate(param: {
+ projectId: string;
+ base: BaseReqType;
+}) {
+ validatePayload('swagger.json#/components/schemas/BaseReq', param.base);
+
+ // type | base | projectId
+ const baseBody = param.base;
+ const project = await Project.getWithInfo(param.projectId);
+ const base = await Base.createBase({
+ ...baseBody,
+ type: baseBody.config?.client,
+ projectId: project.id,
+ });
+
+ await syncBaseMigration(project, base);
+
+ const info = await populateMeta(base, project);
+
+ T.emit('evt_api_created', info);
+
+ delete base.config;
+
+ T.emit('evt', {
+ evt_type: 'base:created',
+ });
+
+ return base;
+}
diff --git a/packages/nocodb/src/lib/services/cacheService.ts b/packages/nocodb/src/lib/services/cacheService.ts
new file mode 100644
index 0000000000..968da7c33f
--- /dev/null
+++ b/packages/nocodb/src/lib/services/cacheService.ts
@@ -0,0 +1,10 @@
+import NocoCache from '../cache/NocoCache';
+
+export async function cacheGet() {
+ return await NocoCache.export();
+}
+
+export async function cacheDelete() {
+ await NocoCache.destroy();
+ return true;
+}
diff --git a/packages/nocodb/src/lib/meta/api/columnApis.ts b/packages/nocodb/src/lib/services/columnService.ts
similarity index 76%
rename from packages/nocodb/src/lib/meta/api/columnApis.ts
rename to packages/nocodb/src/lib/services/columnService.ts
index 593438f6d7..1609f02fe8 100644
--- a/packages/nocodb/src/lib/meta/api/columnApis.ts
+++ b/packages/nocodb/src/lib/services/columnService.ts
@@ -1,16 +1,3 @@
-import { Request, Response, Router } from 'express';
-import Model from '../../models/Model';
-import ProjectMgrv2 from '../../db/sql-mgr/v2/ProjectMgrv2';
-import Base from '../../models/Base';
-import Column from '../../models/Column';
-import { Tele } from 'nc-help';
-import validateParams from '../helpers/validateParams';
-
-import LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn';
-import {
- getUniqueColumnAliasName,
- getUniqueColumnName,
-} from '../helpers/getUniqueName';
import {
AuditOperationSubTypes,
AuditOperationTypes,
@@ -21,32 +8,42 @@ import {
RelationTypes,
substituteColumnAliasWithIdInFormula,
substituteColumnIdWithAliasInFormula,
- TableType,
UITypes,
} from 'nocodb-sdk';
-import Audit from '../../models/Audit';
-import SqlMgrv2 from '../../db/sql-mgr/v2/SqlMgrv2';
-import Noco from '../../Noco';
-import NcMetaIO from '../NcMetaIO';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import { NcError } from '../helpers/catchError';
-import getColumnPropsFromUIDT from '../helpers/getColumnPropsFromUIDT';
-import mapDefaultDisplayValue from '../helpers/mapDefaultDisplayValue';
-import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
-import { metaApiMetrics } from '../helpers/apiMetrics';
-import FormulaColumn from '../../models/FormulaColumn';
-import KanbanView from '../../models/KanbanView';
-import { MetaTable } from '../../utils/globals';
-import formulaQueryBuilderv2 from '../../db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2';
+import formulaQueryBuilderv2 from '../db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2';
+import ProjectMgrv2 from '../db/sql-mgr/v2/ProjectMgrv2';
+import SqlMgrv2 from '../db/sql-mgr/v2/SqlMgrv2';
import {
createHmAndBtColumn,
generateFkName,
- getAjvValidatorMw,
randomID,
validateLookupPayload,
+ validatePayload,
validateRequiredField,
validateRollupPayload,
-} from './helpers';
+} from '../meta/api/helpers';
+import { NcError } from '../meta/helpers/catchError';
+import getColumnPropsFromUIDT from '../meta/helpers/getColumnPropsFromUIDT';
+import {
+ getUniqueColumnAliasName,
+ getUniqueColumnName,
+} from '../meta/helpers/getUniqueName';
+import mapDefaultDisplayValue from '../meta/helpers/mapDefaultDisplayValue';
+import validateParams from '../meta/helpers/validateParams';
+import NcMetaIO from '../meta/NcMetaIO';
+import Audit from '../models/Audit';
+import Base from '../models/Base';
+import Column from '../models/Column';
+import FormulaColumn from '../models/FormulaColumn';
+import KanbanView from '../models/KanbanView';
+import LinkToAnotherRecordColumn from '../models/LinkToAnotherRecordColumn';
+import Model from '../models/Model';
+import Project from '../models/Project';
+import Noco from '../Noco';
+import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2';
+
+import { T } from 'nc-help';
+import { MetaTable } from '../utils/globals';
export enum Altered {
NEW_COLUMN = 1,
@@ -54,1369 +51,1076 @@ export enum Altered {
UPDATE_COLUMN = 8,
}
-export async function columnGet(req: Request, res: Response) {
- res.json(await Column.get({ colId: req.params.columnId }));
-}
+export async function columnUpdate(param: {
+ req?: any;
+ columnId: string;
+ column: ColumnReqType & { colOptions?: any };
+ cookie?: any;
+}) {
+ const { cookie } = param;
+ const column = await Column.get({ colId: param.columnId });
-export async function columnAdd(
- req: Request,
- res: Response
-) {
const table = await Model.getWithInfo({
- id: req.params.tableId,
+ id: column.fk_model_id,
});
const base = await Base.get(table.base_id);
- const project = await base.getProject();
-
- if (req.body.title || req.body.column_name) {
- const dbDriver = NcConnectionMgrv2.get(base);
+ const sqlClient = await NcConnectionMgrv2.getSqlClient(base);
- const sqlClientType = dbDriver.clientType();
+ const sqlClientType = sqlClient.knex.clientType();
- const mxColumnLength = Column.getMaxColumnNameLength(sqlClientType);
+ const mxColumnLength = Column.getMaxColumnNameLength(sqlClientType);
- if ((req.body.title || req.body.column_name).length > mxColumnLength) {
- NcError.badRequest(
- `Column name ${
- req.body.title || req.body.column_name
- } exceeds ${mxColumnLength} characters`
- );
- }
+ if (param.column.column_name.length > mxColumnLength) {
+ NcError.badRequest(
+ `Column name ${param.column.column_name} exceeds ${mxColumnLength} characters`
+ );
}
if (
- !isVirtualCol(req.body) &&
+ !isVirtualCol(param.column) &&
!(await Column.checkTitleAvailable({
- column_name: req.body.column_name,
- fk_model_id: req.params.tableId,
+ column_name: param.column.column_name,
+ fk_model_id: column.fk_model_id,
+ exclude_id: param.columnId,
}))
) {
NcError.badRequest('Duplicate column name');
}
if (
!(await Column.checkAliasAvailable({
- title: req.body.title || req.body.column_name,
- fk_model_id: req.params.tableId,
+ title: param.column.title,
+ fk_model_id: column.fk_model_id,
+ exclude_id: param.columnId,
}))
) {
NcError.badRequest('Duplicate column alias');
}
- let colBody: any = req.body;
- switch (colBody.uidt) {
- case UITypes.Rollup:
- {
- await validateRollupPayload(req.body);
-
- await Column.insert({
+ let colBody = { ...param.column } as Column & {
+ formula?: string;
+ formula_raw?: string;
+ };
+ if (
+ [
+ UITypes.Lookup,
+ UITypes.Rollup,
+ UITypes.LinkToAnotherRecord,
+ UITypes.Formula,
+ UITypes.QrCode,
+ UITypes.Barcode,
+ UITypes.ForeignKey,
+ ].includes(column.uidt)
+ ) {
+ if (column.uidt === colBody.uidt) {
+ if ([UITypes.QrCode, UITypes.Barcode].includes(column.uidt)) {
+ await Column.update(column.id, {
+ ...column,
...colBody,
- fk_model_id: table.id,
- });
- }
- break;
- case UITypes.Lookup:
- {
- await validateLookupPayload(req.body);
+ } as Column);
+ } else if (column.uidt === UITypes.Formula) {
+ colBody.formula = await substituteColumnAliasWithIdInFormula(
+ colBody.formula_raw || colBody.formula,
+ table.columns
+ );
- await Column.insert({
+ try {
+ // test the query to see if it is valid in db level
+ const dbDriver = NcConnectionMgrv2.get(base);
+ await formulaQueryBuilderv2(colBody.formula, null, dbDriver, table);
+ } catch (e) {
+ console.error(e);
+ NcError.badRequest('Invalid Formula');
+ }
+
+ await Column.update(column.id, {
+ // title: colBody.title,
+ ...column,
...colBody,
- fk_model_id: table.id,
+ });
+ } else if (colBody.title !== column.title) {
+ await Column.updateAlias(param.columnId, {
+ title: colBody.title,
});
}
- break;
+ await updateRollupOrLookup(colBody, column);
+ } else {
+ NcError.notImplemented(
+ `Updating ${colBody.uidt} => ${colBody.uidt} is not implemented`
+ );
+ }
+ } else if (
+ [
+ UITypes.Lookup,
+ UITypes.Rollup,
+ UITypes.LinkToAnotherRecord,
+ UITypes.Formula,
+ UITypes.QrCode,
+ UITypes.Barcode,
+ UITypes.ForeignKey,
+ ].includes(colBody.uidt)
+ ) {
+ NcError.notImplemented(
+ `Updating ${colBody.uidt} => ${colBody.uidt} is not implemented`
+ );
+ } else if (
+ [UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt)
+ ) {
+ colBody = getColumnPropsFromUIDT(colBody, base);
- case UITypes.LinkToAnotherRecord:
- {
- validateParams(['parentId', 'childId', 'type'], req.body);
+ const baseModel = await Model.getBaseModelSQL({
+ id: table.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
- // get parent and child models
- const parent = await Model.getWithInfo({
- id: (req.body as LinkToAnotherColumnReqType).parentId,
- });
- const child = await Model.getWithInfo({
- id: (req.body as LinkToAnotherColumnReqType).childId,
- });
- let childColumn: Column;
+ if (colBody.colOptions?.options) {
+ const supportedDrivers = ['mysql', 'mysql2', 'pg', 'mssql', 'sqlite3'];
+ const dbDriver = NcConnectionMgrv2.get(base);
+ const driverType = dbDriver.clientType();
- const sqlMgr = await ProjectMgrv2.getSqlMgr({
- id: base.project_id,
- });
- if (
- (req.body as LinkToAnotherColumnReqType).type === 'hm' ||
- (req.body as LinkToAnotherColumnReqType).type === 'bt'
- ) {
- // populate fk column name
- const fkColName = getUniqueColumnName(
- await child.getColumns(),
- `${parent.table_name}_id`
+ // MultiSelect to SingleSelect
+ if (
+ column.uidt === UITypes.MultiSelect &&
+ colBody.uidt === UITypes.SingleSelect
+ ) {
+ if (driverType === 'mysql' || driverType === 'mysql2') {
+ await dbDriver.raw(
+ `UPDATE ?? SET ?? = SUBSTRING_INDEX(??, ',', 1) WHERE ?? LIKE '%,%';`,
+ [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ column.column_name,
+ ]
);
-
- let foreignKeyName;
- {
- // create foreign key
- const newColumn = {
- cn: fkColName,
-
- title: fkColName,
- column_name: fkColName,
- rqd: false,
- pk: false,
- ai: false,
- cdf: null,
- dt: parent.primaryKey.dt,
- dtxp: parent.primaryKey.dtxp,
- dtxs: parent.primaryKey.dtxs,
- un: parent.primaryKey.un,
- altered: Altered.NEW_COLUMN,
- };
- const tableUpdateBody = {
- ...child,
- tn: child.table_name,
- originalColumns: child.columns.map((c) => ({
- ...c,
- cn: c.column_name,
- })),
- columns: [
- ...child.columns.map((c) => ({
- ...c,
- cn: c.column_name,
- })),
- newColumn,
- ],
- };
-
- await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody);
-
- const { id } = await Column.insert({
- ...newColumn,
- uidt: UITypes.ForeignKey,
- fk_model_id: child.id,
- });
-
- childColumn = await Column.get({ colId: id });
-
- // ignore relation creation if virtual
- if (!(req.body as LinkToAnotherColumnReqType).virtual) {
- foreignKeyName = generateFkName(parent, child);
- // create relation
- await sqlMgr.sqlOpPlus(base, 'relationCreate', {
- childColumn: fkColName,
- childTable: child.table_name,
- parentTable: parent.table_name,
- onDelete: 'NO ACTION',
- onUpdate: 'NO ACTION',
- type: 'real',
- parentColumn: parent.primaryKey.column_name,
- foreignKeyName,
- });
- }
-
- // todo: create index for virtual relations as well
- // create index for foreign key in pg
- if (base.type === 'pg') {
- await createColumnIndex({
- column: new Column({
- ...newColumn,
- fk_model_id: child.id,
- }),
- base,
- sqlMgr,
- });
- }
- }
- await createHmAndBtColumn(
- child,
- parent,
- childColumn,
- (req.body as LinkToAnotherColumnReqType).type as RelationTypes,
- (req.body as LinkToAnotherColumnReqType).title,
- foreignKeyName,
- (req.body as LinkToAnotherColumnReqType).virtual
+ } else if (driverType === 'pg') {
+ await dbDriver.raw(`UPDATE ?? SET ?? = split_part(??, ',', 1);`, [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ ]);
+ } else if (driverType === 'mssql') {
+ await dbDriver.raw(
+ `UPDATE ?? SET ?? = LEFT(cast(?? as varchar(max)), CHARINDEX(',', ??) - 1) WHERE CHARINDEX(',', ??) > 0;`,
+ [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ column.column_name,
+ column.column_name,
+ ]
);
- } else if ((req.body as LinkToAnotherColumnReqType).type === 'mm') {
- const aTn = `${project?.prefix ?? ''}_nc_m2m_${randomID()}`;
- const aTnAlias = aTn;
-
- const parentPK = parent.primaryKey;
- const childPK = child.primaryKey;
-
- const associateTableCols = [];
-
- const parentCn = 'table1_id';
- const childCn = 'table2_id';
-
- associateTableCols.push(
- {
- cn: childCn,
- column_name: childCn,
- title: childCn,
- rqd: true,
- pk: true,
- ai: false,
- cdf: null,
- dt: childPK.dt,
- dtxp: childPK.dtxp,
- dtxs: childPK.dtxs,
- un: childPK.un,
- altered: 1,
- uidt: UITypes.ForeignKey,
- },
- {
- cn: parentCn,
- column_name: parentCn,
- title: parentCn,
- rqd: true,
- pk: true,
- ai: false,
- cdf: null,
- dt: parentPK.dt,
- dtxp: parentPK.dtxp,
- dtxs: parentPK.dtxs,
- un: parentPK.un,
- altered: 1,
- uidt: UITypes.ForeignKey,
- }
+ } else if (driverType === 'sqlite3') {
+ await dbDriver.raw(
+ `UPDATE ?? SET ?? = substr(??, 1, instr(??, ',') - 1) WHERE ?? LIKE '%,%';`,
+ [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ column.column_name,
+ column.column_name,
+ ]
);
+ }
+ }
- await sqlMgr.sqlOpPlus(base, 'tableCreate', {
- tn: aTn,
- _tn: aTnAlias,
- columns: associateTableCols,
- });
-
- const assocModel = await Model.insert(project.id, base.id, {
- table_name: aTn,
- title: aTnAlias,
- // todo: sanitize
- mm: true,
- columns: associateTableCols,
- });
-
- let foreignKeyName1;
- let foreignKeyName2;
-
- if (!(req.body as LinkToAnotherColumnReqType).virtual) {
- foreignKeyName1 = generateFkName(parent, child);
- foreignKeyName2 = generateFkName(parent, child);
-
- const rel1Args = {
- ...req.body,
- childTable: aTn,
- childColumn: parentCn,
- parentTable: parent.table_name,
- parentColumn: parentPK.column_name,
- type: 'real',
- foreignKeyName: foreignKeyName1,
- };
- const rel2Args = {
- ...req.body,
- childTable: aTn,
- childColumn: childCn,
- parentTable: child.table_name,
- parentColumn: childPK.column_name,
- type: 'real',
- foreignKeyName: foreignKeyName2,
- };
+ // Handle migrations
+ if (column.colOptions?.options) {
+ for (const op of column.colOptions.options.filter(
+ (el) => el.order === null
+ )) {
+ op.title = op.title.replace(/^'/, '').replace(/'$/, '');
+ }
+ }
- await sqlMgr.sqlOpPlus(base, 'relationCreate', rel1Args);
- await sqlMgr.sqlOpPlus(base, 'relationCreate', rel2Args);
+ // Handle default values
+ const optionTitles = colBody.colOptions.options.map((el) =>
+ el.title.replace(/'/g, "''")
+ );
+ if (colBody.cdf) {
+ if (colBody.uidt === UITypes.SingleSelect) {
+ if (!optionTitles.includes(colBody.cdf.replace(/'/g, "''"))) {
+ NcError.badRequest(
+ `Default value '${colBody.cdf}' is not a select option.`
+ );
}
- const parentCol = (await assocModel.getColumns())?.find(
- (c) => c.column_name === parentCn
- );
- const childCol = (await assocModel.getColumns())?.find(
- (c) => c.column_name === childCn
- );
-
- await createHmAndBtColumn(
- assocModel,
- child,
- childCol,
- null,
- null,
- foreignKeyName1,
- (req.body as LinkToAnotherColumnReqType).virtual,
- true
- );
- await createHmAndBtColumn(
- assocModel,
- parent,
- parentCol,
- null,
- null,
- foreignKeyName2,
- (req.body as LinkToAnotherColumnReqType).virtual,
- true
- );
-
- await Column.insert({
- title: getUniqueColumnAliasName(
- await child.getColumns(),
- `${parent.title} List`
- ),
- uidt: UITypes.LinkToAnotherRecord,
- type: 'mm',
-
- // ref_db_alias
- fk_model_id: child.id,
- // db_type:
-
- fk_child_column_id: childPK.id,
- fk_parent_column_id: parentPK.id,
-
- fk_mm_model_id: assocModel.id,
- fk_mm_child_column_id: childCol.id,
- fk_mm_parent_column_id: parentCol.id,
- fk_related_model_id: parent.id,
- });
- await Column.insert({
- title: getUniqueColumnAliasName(
- await parent.getColumns(),
- req.body.title ?? `${child.title} List`
- ),
-
- uidt: UITypes.LinkToAnotherRecord,
- type: 'mm',
-
- fk_model_id: parent.id,
-
- fk_child_column_id: parentPK.id,
- fk_parent_column_id: childPK.id,
+ } else {
+ for (const cdf of colBody.cdf.split(',')) {
+ if (!optionTitles.includes(cdf.replace(/'/g, "''"))) {
+ NcError.badRequest(
+ `Default value '${cdf}' is not a select option.`
+ );
+ }
+ }
+ }
- fk_mm_model_id: assocModel.id,
- fk_mm_child_column_id: parentCol.id,
- fk_mm_parent_column_id: childCol.id,
- fk_related_model_id: child.id,
- });
+ // handle single quote for default value
+ if (driverType === 'mysql' || driverType === 'mysql2') {
+ colBody.cdf = colBody.cdf.replace(/'/g, "'");
+ } else {
+ colBody.cdf = colBody.cdf.replace(/'/g, "''");
+ }
- // todo: create index for virtual relations as well
- // create index for foreign key in pg
- if (base.type === 'pg') {
- await createColumnIndex({
- column: new Column({
- ...associateTableCols[0],
- fk_model_id: assocModel.id,
- }),
- base,
- sqlMgr,
- });
- await createColumnIndex({
- column: new Column({
- ...associateTableCols[1],
- fk_model_id: assocModel.id,
- }),
- base,
- sqlMgr,
- });
- }
+ if (driverType === 'pg') {
+ colBody.cdf = `'${colBody.cdf}'`;
}
}
- Tele.emit('evt', { evt_type: 'relation:created' });
- break;
- case UITypes.QrCode:
- await Column.insert({
- ...colBody,
- fk_model_id: table.id,
- });
- break;
- case UITypes.Barcode:
- await Column.insert({
- ...colBody,
- fk_model_id: table.id,
- });
- break;
- case UITypes.Formula:
- colBody.formula = await substituteColumnAliasWithIdInFormula(
- colBody.formula_raw || colBody.formula,
- table.columns
- );
-
- try {
- // test the query to see if it is valid in db level
- const dbDriver = NcConnectionMgrv2.get(base);
- await formulaQueryBuilderv2(colBody.formula, null, dbDriver, table);
- } catch (e) {
- console.error(e);
- NcError.badRequest('Invalid Formula');
+ // Restrict duplicates
+ const titles = colBody.colOptions.options.map((el) => el.title);
+ if (
+ titles.some(function (item) {
+ return titles.indexOf(item) !== titles.lastIndexOf(item);
+ })
+ ) {
+ NcError.badRequest('Duplicates are not allowed!');
}
- await Column.insert({
- ...colBody,
- fk_model_id: table.id,
- });
+ // Restrict empty options
+ if (
+ titles.some(function (item) {
+ return item === '';
+ })
+ ) {
+ NcError.badRequest('Empty options are not allowed!');
+ }
- break;
- default:
- {
- colBody = getColumnPropsFromUIDT(colBody, base);
- if (colBody.uidt === UITypes.Duration) {
- colBody.dtxp = '20';
- // by default, colBody.dtxs is 2
- // Duration column needs more that that
- colBody.dtxs = '4';
+ // Trim end of enum/set
+ if (colBody.dt === 'enum' || colBody.dt === 'set') {
+ for (const opt of colBody.colOptions.options) {
+ opt.title = opt.title.trimEnd();
}
+ }
+
+ if (colBody.uidt === UITypes.SingleSelect) {
+ colBody.dtxp = colBody.colOptions?.options.length
+ ? `${colBody.colOptions.options
+ .map((o) => `'${o.title.replace(/'/gi, "''")}'`)
+ .join(',')}`
+ : '';
+ } else if (colBody.uidt === UITypes.MultiSelect) {
+ colBody.dtxp = colBody.colOptions?.options.length
+ ? `${colBody.colOptions.options
+ .map((o) => {
+ if (o.title.includes(',')) {
+ NcError.badRequest("Illegal char(',') for MultiSelect");
+ }
+ return `'${o.title.replace(/'/gi, "''")}'`;
+ })
+ .join(',')}`
+ : '';
+ }
+ // Handle empty enum/set for mysql (we restrict empty user options beforehand)
+ if (driverType === 'mysql' || driverType === 'mysql2') {
if (
- [UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt)
+ !colBody.colOptions.options.length &&
+ (!colBody.dtxp || colBody.dtxp === '')
) {
- const dbDriver = NcConnectionMgrv2.get(base);
- const driverType = dbDriver.clientType();
- const optionTitles = colBody.colOptions.options.map((el) =>
- el.title.replace(/'/g, "''")
- );
+ colBody.dtxp = "''";
+ }
- // this is not used for select columns and cause issue for MySQL
- colBody.dtxs = '';
- // Handle default values
- if (colBody.cdf) {
- if (colBody.uidt === UITypes.SingleSelect) {
- if (!optionTitles.includes(colBody.cdf.replace(/'/g, "''"))) {
- NcError.badRequest(
- `Default value '${colBody.cdf}' is not a select option.`
- );
- }
- } else {
- for (const cdf of colBody.cdf.split(',')) {
- if (!optionTitles.includes(cdf.replace(/'/g, "''"))) {
- NcError.badRequest(
- `Default value '${cdf}' is not a select option.`
- );
- }
- }
- }
+ if (colBody.dt === 'set') {
+ if (colBody.colOptions?.options.length > 64) {
+ colBody.dt = 'text';
+ }
+ }
+ }
- // handle single quote for default value
- if (driverType === 'mysql' || driverType === 'mysql2') {
- colBody.cdf = colBody.cdf.replace(/'/g, "'");
+ // Handle option delete
+ if (column.colOptions?.options) {
+ for (const option of column.colOptions.options.filter((oldOp) =>
+ colBody.colOptions.options.find((newOp) => newOp.id === oldOp.id)
+ ? false
+ : true
+ )) {
+ if (
+ !supportedDrivers.includes(driverType) &&
+ column.uidt === UITypes.MultiSelect
+ ) {
+ NcError.badRequest(
+ 'Your database not yet supported for this operation. Please remove option from records manually before dropping.'
+ );
+ }
+ if (column.uidt === UITypes.SingleSelect) {
+ if (driverType === 'mssql') {
+ await dbDriver.raw(`UPDATE ?? SET ?? = NULL WHERE ?? LIKE ?`, [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ option.title,
+ ]);
} else {
- colBody.cdf = colBody.cdf.replace(/'/g, "''");
+ await baseModel.bulkUpdateAll(
+ { where: `(${column.title},eq,${option.title})` },
+ { [column.column_name]: null },
+ { cookie }
+ );
}
-
- if (driverType === 'pg') {
- colBody.cdf = `'${colBody.cdf}'`;
+ } else if (column.uidt === UITypes.MultiSelect) {
+ if (driverType === 'mysql' || driverType === 'mysql2') {
+ if (colBody.dt === 'set') {
+ await dbDriver.raw(
+ `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), ',')) WHERE FIND_IN_SET(?, ??)`,
+ [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ option.title,
+ option.title,
+ column.column_name,
+ ]
+ );
+ } else {
+ await dbDriver.raw(
+ `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), ','))`,
+ [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ option.title,
+ ]
+ );
+ }
+ } else if (driverType === 'pg') {
+ await dbDriver.raw(
+ `UPDATE ?? SET ?? = array_to_string(array_remove(string_to_array(??, ','), ?), ',')`,
+ [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ option.title,
+ ]
+ );
+ } else if (driverType === 'mssql') {
+ await dbDriver.raw(
+ `UPDATE ?? SET ?? = substring(replace(concat(',', ??, ','), concat(',', ?, ','), ','), 2, len(replace(concat(',', ??, ','), concat(',', ?, ','), ',')) - 2)`,
+ [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ option.title,
+ column.column_name,
+ option.title,
+ ]
+ );
+ } else if (driverType === 'sqlite3') {
+ await dbDriver.raw(
+ `UPDATE ?? SET ?? = TRIM(REPLACE(',' || ?? || ',', ',' || ? || ',', ','), ',')`,
+ [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ option.title,
+ ]
+ );
}
}
+ }
+ }
- // Restrict duplicates
- const titles = colBody.colOptions.options.map((el) => el.title);
- if (
- titles.some(function (item) {
- return titles.indexOf(item) !== titles.lastIndexOf(item);
- })
- ) {
- NcError.badRequest('Duplicates are not allowed!');
- }
+ const interchange = [];
- // Restrict empty options
+ // Handle option update
+ if (column.colOptions?.options) {
+ const old_titles = column.colOptions.options.map((el) => el.title);
+ for (const option of column.colOptions.options.filter((oldOp) =>
+ colBody.colOptions.options.find(
+ (newOp) => newOp.id === oldOp.id && newOp.title !== oldOp.title
+ )
+ )) {
if (
- titles.some(function (item) {
- return item === '';
- })
+ !supportedDrivers.includes(driverType) &&
+ column.uidt === UITypes.MultiSelect
) {
- NcError.badRequest('Empty options are not allowed!');
+ NcError.badRequest(
+ 'Your database not yet supported for this operation. Please remove option from records manually before updating.'
+ );
}
- // Trim end of enum/set
- if (colBody.dt === 'enum' || colBody.dt === 'set') {
- for (const opt of colBody.colOptions.options) {
- opt.title = opt.title.trimEnd();
+ const newOp = {
+ ...colBody.colOptions.options.find((el) => option.id === el.id),
+ };
+ if (old_titles.includes(newOp.title)) {
+ const def_option = { ...newOp };
+ let title_counter = 1;
+ while (old_titles.includes(newOp.title)) {
+ newOp.title = `${def_option.title}_${title_counter++}`;
}
+ interchange.push({
+ def_option,
+ temp_title: newOp.title,
+ });
}
- if (colBody.uidt === UITypes.SingleSelect) {
- colBody.dtxp = colBody.colOptions?.options.length
- ? `${colBody.colOptions.options
- .map((o) => `'${o.title.replace(/'/gi, "''")}'`)
- .join(',')}`
- : '';
- } else if (colBody.uidt === UITypes.MultiSelect) {
- colBody.dtxp = colBody.colOptions?.options.length
- ? `${colBody.colOptions.options
- .map((o) => {
- if (o.title.includes(',')) {
- NcError.badRequest("Illegal char(',') for MultiSelect");
- }
- return `'${o.title.replace(/'/gi, "''")}'`;
- })
- .join(',')}`
- : '';
- }
-
- // Handle empty enum/set for mysql (we restrict empty user options beforehand)
- if (driverType === 'mysql' || driverType === 'mysql2') {
- if (
- !colBody.colOptions.options.length &&
- (!colBody.dtxp || colBody.dtxp === '')
- ) {
- colBody.dtxp = "''";
- }
-
- if (colBody.dt === 'set') {
- if (colBody.colOptions?.options.length > 64) {
- colBody.dt = 'text';
- }
- }
- }
- }
-
- const tableUpdateBody = {
- ...table,
- tn: table.table_name,
- originalColumns: table.columns.map((c) => ({
- ...c,
- cn: c.column_name,
- })),
- columns: [
- ...table.columns.map((c) => ({ ...c, cn: c.column_name })),
- {
- ...colBody,
- cn: colBody.column_name,
- altered: Altered.NEW_COLUMN,
- },
- ],
- };
-
- const sqlClient = await NcConnectionMgrv2.getSqlClient(base);
- const sqlMgr = await ProjectMgrv2.getSqlMgr({ id: base.project_id });
- await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody);
-
- const columns: Array<
- Omit & {
- cn: string;
- system?: boolean;
- }
- > = (await sqlClient.columnList({ tn: table.table_name }))?.data?.list;
-
- const insertedColumnMeta =
- columns.find((c) => c.cn === colBody.column_name) || ({} as any);
-
- await Column.insert({
- ...colBody,
- ...insertedColumnMeta,
- dtxp: [UITypes.MultiSelect, UITypes.SingleSelect].includes(
- colBody.uidt as any
- )
- ? colBody.dtxp
- : insertedColumnMeta.dtxp,
- fk_model_id: table.id,
- });
- }
- break;
- }
-
- await table.getColumns();
-
- await Audit.insert({
- project_id: base.project_id,
- op_type: AuditOperationTypes.TABLE_COLUMN,
- op_sub_type: AuditOperationSubTypes.CREATED,
- user: (req as any)?.user?.email,
- description: `created column ${colBody.column_name} with alias ${colBody.title} from table ${table.table_name}`,
- ip: (req as any).clientIp,
- }).then(() => {});
-
- Tele.emit('evt', { evt_type: 'column:created' });
-
- res.json(table);
-}
-
-export async function columnSetAsPrimary(req: Request, res: Response) {
- const column = await Column.get({ colId: req.params.columnId });
- res.json(await Model.updatePrimaryColumn(column.fk_model_id, column.id));
-}
-
-async function updateRollupOrLookup(colBody: any, column: Column) {
- if (
- UITypes.Lookup === column.uidt &&
- validateRequiredField(colBody, [
- 'fk_lookup_column_id',
- 'fk_relation_column_id',
- ])
- ) {
- await validateLookupPayload(colBody, column.id);
- await Column.update(column.id, colBody);
- } else if (
- UITypes.Rollup === column.uidt &&
- validateRequiredField(colBody, [
- 'fk_relation_column_id',
- 'fk_rollup_column_id',
- 'rollup_function',
- ])
- ) {
- await validateRollupPayload(colBody);
- await Column.update(column.id, colBody);
- }
-}
-
-export async function columnUpdate(req: Request, res: Response) {
- const column = await Column.get({ colId: req.params.columnId });
-
- const table = await Model.getWithInfo({
- id: column.fk_model_id,
- });
-
- const base = await Base.get(table.base_id);
-
- const sqlClient = await NcConnectionMgrv2.getSqlClient(base);
-
- const sqlClientType = sqlClient.knex.clientType();
-
- const mxColumnLength = Column.getMaxColumnNameLength(sqlClientType);
-
- if (req.body.column_name.length > mxColumnLength) {
- NcError.badRequest(
- `Column name ${req.body.column_name} exceeds ${mxColumnLength} characters`
- );
- }
-
- if (
- !isVirtualCol(req.body) &&
- !(await Column.checkTitleAvailable({
- column_name: req.body.column_name,
- fk_model_id: column.fk_model_id,
- exclude_id: req.params.columnId,
- }))
- ) {
- NcError.badRequest('Duplicate column name');
- }
- if (
- !(await Column.checkAliasAvailable({
- title: req.body.title,
- fk_model_id: column.fk_model_id,
- exclude_id: req.params.columnId,
- }))
- ) {
- NcError.badRequest('Duplicate column alias');
- }
-
- let colBody = req.body;
- if (
- [
- UITypes.Lookup,
- UITypes.Rollup,
- UITypes.LinkToAnotherRecord,
- UITypes.Formula,
- UITypes.QrCode,
- UITypes.Barcode,
- UITypes.ForeignKey,
- ].includes(column.uidt)
- ) {
- if (column.uidt === colBody.uidt) {
- if ([UITypes.QrCode, UITypes.Barcode].includes(column.uidt)) {
- await Column.update(column.id, {
- ...column,
- ...colBody,
- });
- } else if (column.uidt === UITypes.Formula) {
- colBody.formula = await substituteColumnAliasWithIdInFormula(
- colBody.formula_raw || colBody.formula,
- table.columns
- );
-
- try {
- // test the query to see if it is valid in db level
- const dbDriver = NcConnectionMgrv2.get(base);
- await formulaQueryBuilderv2(colBody.formula, null, dbDriver, table);
- } catch (e) {
- console.error(e);
- NcError.badRequest('Invalid Formula');
- }
-
- await Column.update(column.id, {
- // title: colBody.title,
- ...column,
- ...colBody,
- });
- } else if (colBody.title !== column.title) {
- await Column.updateAlias(req.params.columnId, {
- title: colBody.title,
- });
- }
- await updateRollupOrLookup(colBody, column);
- } else {
- NcError.notImplemented(
- `Updating ${colBody.uidt} => ${colBody.uidt} is not implemented`
- );
- }
- } else if (
- [
- UITypes.Lookup,
- UITypes.Rollup,
- UITypes.LinkToAnotherRecord,
- UITypes.Formula,
- UITypes.QrCode,
- UITypes.Barcode,
- UITypes.ForeignKey,
- ].includes(colBody.uidt)
- ) {
- NcError.notImplemented(
- `Updating ${colBody.uidt} => ${colBody.uidt} is not implemented`
- );
- } else if (
- [UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt)
- ) {
- colBody = getColumnPropsFromUIDT(colBody, base);
-
- const baseModel = await Model.getBaseModelSQL({
- id: table.id,
- dbDriver: NcConnectionMgrv2.get(base),
- });
-
- if (colBody.colOptions?.options) {
- const supportedDrivers = ['mysql', 'mysql2', 'pg', 'mssql', 'sqlite3'];
- const dbDriver = NcConnectionMgrv2.get(base);
- const driverType = dbDriver.clientType();
-
- // MultiSelect to SingleSelect
- if (
- column.uidt === UITypes.MultiSelect &&
- colBody.uidt === UITypes.SingleSelect
- ) {
- if (driverType === 'mysql' || driverType === 'mysql2') {
- await dbDriver.raw(
- `UPDATE ?? SET ?? = SUBSTRING_INDEX(??, ',', 1) WHERE ?? LIKE '%,%';`,
- [
- table.table_name,
- column.column_name,
- column.column_name,
- column.column_name,
- ]
- );
- } else if (driverType === 'pg') {
- await dbDriver.raw(`UPDATE ?? SET ?? = split_part(??, ',', 1);`, [
- table.table_name,
- column.column_name,
- column.column_name,
- ]);
- } else if (driverType === 'mssql') {
- await dbDriver.raw(
- `UPDATE ?? SET ?? = LEFT(cast(?? as varchar(max)), CHARINDEX(',', ??) - 1) WHERE CHARINDEX(',', ??) > 0;`,
- [
- table.table_name,
- column.column_name,
- column.column_name,
- column.column_name,
- column.column_name,
- ]
- );
- } else if (driverType === 'sqlite3') {
- await dbDriver.raw(
- `UPDATE ?? SET ?? = substr(??, 1, instr(??, ',') - 1) WHERE ?? LIKE '%,%';`,
- [
- table.table_name,
- column.column_name,
- column.column_name,
- column.column_name,
- column.column_name,
- ]
- );
- }
- }
-
- // Handle migrations
- if (column.colOptions?.options) {
- for (const op of column.colOptions.options.filter(
- (el) => el.order === null
- )) {
- op.title = op.title.replace(/^'/, '').replace(/'$/, '');
- }
- }
-
- // Handle default values
- const optionTitles = colBody.colOptions.options.map((el) =>
- el.title.replace(/'/g, "''")
- );
- if (colBody.cdf) {
- if (colBody.uidt === UITypes.SingleSelect) {
- if (!optionTitles.includes(colBody.cdf.replace(/'/g, "''"))) {
- NcError.badRequest(
- `Default value '${colBody.cdf}' is not a select option.`
- );
- }
- } else {
- for (const cdf of colBody.cdf.split(',')) {
- if (!optionTitles.includes(cdf.replace(/'/g, "''"))) {
- NcError.badRequest(
- `Default value '${cdf}' is not a select option.`
- );
- }
- }
- }
-
- // handle single quote for default value
- if (driverType === 'mysql' || driverType === 'mysql2') {
- colBody.cdf = colBody.cdf.replace(/'/g, "'");
- } else {
- colBody.cdf = colBody.cdf.replace(/'/g, "''");
- }
-
- if (driverType === 'pg') {
- colBody.cdf = `'${colBody.cdf}'`;
- }
- }
-
- // Restrict duplicates
- const titles = colBody.colOptions.options.map((el) => el.title);
- if (
- titles.some(function (item) {
- return titles.indexOf(item) !== titles.lastIndexOf(item);
- })
- ) {
- NcError.badRequest('Duplicates are not allowed!');
- }
-
- // Restrict empty options
- if (
- titles.some(function (item) {
- return item === '';
- })
- ) {
- NcError.badRequest('Empty options are not allowed!');
- }
+ // Append new option before editing
+ if (
+ (driverType === 'mysql' || driverType === 'mysql2') &&
+ (column.dt === 'enum' || column.dt === 'set')
+ ) {
+ column.colOptions.options.push({ title: newOp.title });
- // Trim end of enum/set
- if (colBody.dt === 'enum' || colBody.dt === 'set') {
- for (const opt of colBody.colOptions.options) {
- opt.title = opt.title.trimEnd();
- }
- }
+ let temp_dtxp = '';
+ if (column.uidt === UITypes.SingleSelect) {
+ temp_dtxp = column.colOptions.options.length
+ ? `${column.colOptions.options
+ .map((o) => `'${o.title.replace(/'/gi, "''")}'`)
+ .join(',')}`
+ : '';
+ } else if (column.uidt === UITypes.MultiSelect) {
+ temp_dtxp = column.colOptions.options.length
+ ? `${column.colOptions.options
+ .map((o) => {
+ if (o.title.includes(',')) {
+ NcError.badRequest("Illegal char(',') for MultiSelect");
+ throw new Error('');
+ }
+ return `'${o.title.replace(/'/gi, "''")}'`;
+ })
+ .join(',')}`
+ : '';
+ }
- if (colBody.uidt === UITypes.SingleSelect) {
- colBody.dtxp = colBody.colOptions?.options.length
- ? `${colBody.colOptions.options
- .map((o) => `'${o.title.replace(/'/gi, "''")}'`)
- .join(',')}`
- : '';
- } else if (colBody.uidt === UITypes.MultiSelect) {
- colBody.dtxp = colBody.colOptions?.options.length
- ? `${colBody.colOptions.options
- .map((o) => {
- if (o.title.includes(',')) {
- NcError.badRequest("Illegal char(',') for MultiSelect");
- }
- return `'${o.title.replace(/'/gi, "''")}'`;
- })
- .join(',')}`
- : '';
- }
+ const tableUpdateBody = {
+ ...table,
+ tn: table.table_name,
+ originalColumns: table.columns.map((c) => ({
+ ...c,
+ cn: c.column_name,
+ cno: c.column_name,
+ })),
+ columns: await Promise.all(
+ table.columns.map(async (c) => {
+ if (c.id === param.columnId) {
+ const res = {
+ ...c,
+ ...column,
+ cn: column.column_name,
+ cno: c.column_name,
+ dtxp: temp_dtxp,
+ altered: Altered.UPDATE_COLUMN,
+ };
+ return Promise.resolve(res);
+ } else {
+ (c as any).cn = c.column_name;
+ }
+ return Promise.resolve(c);
+ })
+ ),
+ };
- // Handle empty enum/set for mysql (we restrict empty user options beforehand)
- if (driverType === 'mysql' || driverType === 'mysql2') {
- if (
- !colBody.colOptions.options.length &&
- (!colBody.dtxp || colBody.dtxp === '')
- ) {
- colBody.dtxp = "''";
- }
+ const sqlMgr = await ProjectMgrv2.getSqlMgr({
+ id: base.project_id,
+ });
+ await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody);
- if (colBody.dt === 'set') {
- if (colBody.colOptions?.options.length > 64) {
- colBody.dt = 'text';
+ await Column.update(param.columnId, {
+ ...column,
+ });
}
- }
- }
- // Handle option delete
- if (column.colOptions?.options) {
- for (const option of column.colOptions.options.filter((oldOp) =>
- colBody.colOptions.options.find((newOp) => newOp.id === oldOp.id)
- ? false
- : true
- )) {
- if (
- !supportedDrivers.includes(driverType) &&
- column.uidt === UITypes.MultiSelect
- ) {
- NcError.badRequest(
- 'Your database not yet supported for this operation. Please remove option from records manually before dropping.'
- );
- }
if (column.uidt === UITypes.SingleSelect) {
if (driverType === 'mssql') {
- await dbDriver.raw(`UPDATE ?? SET ?? = NULL WHERE ?? LIKE ?`, [
+ await dbDriver.raw(`UPDATE ?? SET ?? = ? WHERE ?? LIKE ?`, [
table.table_name,
column.column_name,
+ newOp.title,
column.column_name,
option.title,
]);
} else {
await baseModel.bulkUpdateAll(
{ where: `(${column.title},eq,${option.title})` },
- { [column.column_name]: null },
- { cookie: req }
+ { [column.column_name]: newOp.title },
+ { cookie }
);
}
} else if (column.uidt === UITypes.MultiSelect) {
if (driverType === 'mysql' || driverType === 'mysql2') {
if (colBody.dt === 'set') {
await dbDriver.raw(
- `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), ',')) WHERE FIND_IN_SET(?, ??)`,
+ `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), CONCAT(',', ?, ','))) WHERE FIND_IN_SET(?, ??)`,
[
table.table_name,
column.column_name,
column.column_name,
option.title,
+ newOp.title,
option.title,
column.column_name,
]
);
} else {
await dbDriver.raw(
- `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), ','))`,
+ `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), CONCAT(',', ?, ',')))`,
[
table.table_name,
column.column_name,
column.column_name,
option.title,
+ newOp.title,
]
);
}
} else if (driverType === 'pg') {
await dbDriver.raw(
- `UPDATE ?? SET ?? = array_to_string(array_remove(string_to_array(??, ','), ?), ',')`,
+ `UPDATE ?? SET ?? = array_to_string(array_replace(string_to_array(??, ','), ?, ?), ',')`,
[
table.table_name,
column.column_name,
column.column_name,
option.title,
+ newOp.title,
]
);
} else if (driverType === 'mssql') {
await dbDriver.raw(
- `UPDATE ?? SET ?? = substring(replace(concat(',', ??, ','), concat(',', ?, ','), ','), 2, len(replace(concat(',', ??, ','), concat(',', ?, ','), ',')) - 2)`,
+ `UPDATE ?? SET ?? = substring(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ',')), 2, len(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ','))) - 2)`,
[
table.table_name,
column.column_name,
column.column_name,
option.title,
+ newOp.title,
column.column_name,
option.title,
+ newOp.title,
]
);
} else if (driverType === 'sqlite3') {
await dbDriver.raw(
- `UPDATE ?? SET ?? = TRIM(REPLACE(',' || ?? || ',', ',' || ? || ',', ','), ',')`,
+ `UPDATE ?? SET ?? = TRIM(REPLACE(',' || ?? || ',', ',' || ? || ',', ',' || ? || ','), ',')`,
[
table.table_name,
column.column_name,
column.column_name,
option.title,
+ newOp.title,
+ ]
+ );
+ }
+ }
+ }
+ }
+
+ for (const ch of interchange) {
+ const newOp = ch.def_option;
+ if (column.uidt === UITypes.SingleSelect) {
+ if (driverType === 'mssql') {
+ await dbDriver.raw(`UPDATE ?? SET ?? = ? WHERE ?? LIKE ?`, [
+ table.table_name,
+ column.column_name,
+ newOp.title,
+ column.column_name,
+ ch.temp_title,
+ ]);
+ } else {
+ await baseModel.bulkUpdateAll(
+ { where: `(${column.title},eq,${ch.temp_title})` },
+ { [column.column_name]: newOp.title },
+ { cookie }
+ );
+ }
+ } else if (column.uidt === UITypes.MultiSelect) {
+ if (driverType === 'mysql' || driverType === 'mysql2') {
+ if (colBody.dt === 'set') {
+ await dbDriver.raw(
+ `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), CONCAT(',', ?, ','))) WHERE FIND_IN_SET(?, ??)`,
+ [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ ch.temp_title,
+ newOp.title,
+ ch.temp_title,
+ column.column_name,
+ ]
+ );
+ } else {
+ await dbDriver.raw(
+ `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), CONCAT(',', ?, ',')))`,
+ [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ ch.temp_title,
+ newOp.title,
+ ch.temp_title,
+ column.column_name,
]
);
}
+ } else if (driverType === 'pg') {
+ await dbDriver.raw(
+ `UPDATE ?? SET ?? = array_to_string(array_replace(string_to_array(??, ','), ?, ?), ',')`,
+ [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ ch.temp_title,
+ newOp.title,
+ ]
+ );
+ } else if (driverType === 'mssql') {
+ await dbDriver.raw(
+ `UPDATE ?? SET ?? = substring(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ',')), 2, len(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ','))) - 2)`,
+ [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ ch.temp_title,
+ newOp.title,
+ column.column_name,
+ ch.temp_title,
+ newOp.title,
+ ]
+ );
+ } else if (driverType === 'sqlite3') {
+ await dbDriver.raw(
+ `UPDATE ?? SET ?? = TRIM(REPLACE(',' || ?? || ',', ',' || ? || ',', ',' || ? || ','), ',')`,
+ [
+ table.table_name,
+ column.column_name,
+ column.column_name,
+ ch.temp_title,
+ newOp.title,
+ ]
+ );
+ }
+ }
+ }
+ }
+
+ const tableUpdateBody = {
+ ...table,
+ tn: table.table_name,
+ originalColumns: table.columns.map((c) => ({
+ ...c,
+ cn: c.column_name,
+ cno: c.column_name,
+ })),
+ columns: await Promise.all(
+ table.columns.map(async (c) => {
+ if (c.id === param.columnId) {
+ const res = {
+ ...c,
+ ...colBody,
+ cn: colBody.column_name,
+ cno: c.column_name,
+ altered: Altered.UPDATE_COLUMN,
+ };
+
+ // update formula with new column name
+ if (c.column_name != colBody.column_name) {
+ const formulas = await Noco.ncMeta
+ .knex(MetaTable.COL_FORMULA)
+ .where('formula', 'like', `%${c.id}%`);
+ if (formulas) {
+ const new_column = c;
+ new_column.column_name = colBody.column_name;
+ new_column.title = colBody.title;
+ for (const f of formulas) {
+ // the formula with column IDs only
+ const formula = f.formula;
+ // replace column IDs with alias to get the new formula_raw
+ const new_formula_raw = substituteColumnIdWithAliasInFormula(
+ formula,
+ [new_column]
+ );
+ await FormulaColumn.update(c.id, {
+ formula_raw: new_formula_raw,
+ });
+ }
+ }
+ }
+ return Promise.resolve(res);
+ } else {
+ (c as any).cn = c.column_name;
}
- }
- }
+ return Promise.resolve(c);
+ })
+ ),
+ };
- const interchange = [];
+ const sqlMgr = await ProjectMgrv2.getSqlMgr({ id: base.project_id });
+ await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody);
- // Handle option update
- if (column.colOptions?.options) {
- const old_titles = column.colOptions.options.map((el) => el.title);
- for (const option of column.colOptions.options.filter((oldOp) =>
- colBody.colOptions.options.find(
- (newOp) => newOp.id === oldOp.id && newOp.title !== oldOp.title
- )
- )) {
- if (
- !supportedDrivers.includes(driverType) &&
- column.uidt === UITypes.MultiSelect
- ) {
- NcError.badRequest(
- 'Your database not yet supported for this operation. Please remove option from records manually before updating.'
- );
- }
+ await Column.update(param.columnId, {
+ ...colBody,
+ });
+ } else {
+ colBody = getColumnPropsFromUIDT(colBody, base);
+ const tableUpdateBody = {
+ ...table,
+ tn: table.table_name,
+ originalColumns: table.columns.map((c) => ({
+ ...c,
+ cn: c.column_name,
+ cno: c.column_name,
+ })),
+ columns: await Promise.all(
+ table.columns.map(async (c) => {
+ if (c.id === param.columnId) {
+ const res = {
+ ...c,
+ ...colBody,
+ cn: colBody.column_name,
+ cno: c.column_name,
+ altered: Altered.UPDATE_COLUMN,
+ };
- const newOp = {
- ...colBody.colOptions.options.find((el) => option.id === el.id),
- };
- if (old_titles.includes(newOp.title)) {
- const def_option = { ...newOp };
- let title_counter = 1;
- while (old_titles.includes(newOp.title)) {
- newOp.title = `${def_option.title}_${title_counter++}`;
+ // update formula with new column name
+ if (c.column_name != colBody.column_name) {
+ const formulas = await Noco.ncMeta
+ .knex(MetaTable.COL_FORMULA)
+ .where('formula', 'like', `%${c.id}%`);
+ if (formulas) {
+ const new_column = c;
+ new_column.column_name = colBody.column_name;
+ new_column.title = colBody.title;
+ for (const f of formulas) {
+ // the formula with column IDs only
+ const formula = f.formula;
+ // replace column IDs with alias to get the new formula_raw
+ const new_formula_raw = substituteColumnIdWithAliasInFormula(
+ formula,
+ [new_column]
+ );
+ await FormulaColumn.update(c.id, {
+ formula_raw: new_formula_raw,
+ });
+ }
+ }
}
- interchange.push({
- def_option,
- temp_title: newOp.title,
- });
+ return Promise.resolve(res);
+ } else {
+ (c as any).cn = c.column_name;
}
+ return Promise.resolve(c);
+ })
+ ),
+ };
- // Append new option before editing
- if (
- (driverType === 'mysql' || driverType === 'mysql2') &&
- (column.dt === 'enum' || column.dt === 'set')
- ) {
- column.colOptions.options.push({ title: newOp.title });
+ const sqlMgr = await ProjectMgrv2.getSqlMgr({ id: base.project_id });
+ await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody);
- let temp_dtxp = '';
- if (column.uidt === UITypes.SingleSelect) {
- temp_dtxp = column.colOptions.options.length
- ? `${column.colOptions.options
- .map((o) => `'${o.title.replace(/'/gi, "''")}'`)
- .join(',')}`
- : '';
- } else if (column.uidt === UITypes.MultiSelect) {
- temp_dtxp = column.colOptions.options.length
- ? `${column.colOptions.options
- .map((o) => {
- if (o.title.includes(',')) {
- NcError.badRequest("Illegal char(',') for MultiSelect");
- throw new Error('');
- }
- return `'${o.title.replace(/'/gi, "''")}'`;
- })
- .join(',')}`
- : '';
- }
+ await Column.update(param.columnId, {
+ ...colBody,
+ });
+ }
+ await Audit.insert({
+ project_id: base.project_id,
+ op_type: AuditOperationTypes.TABLE_COLUMN,
+ op_sub_type: AuditOperationSubTypes.UPDATED,
+ user: param.req?.user?.email,
+ description: `updated column ${column.column_name} with alias ${column.title} from table ${table.table_name}`,
+ ip: param.req?.clientIp,
+ }).then(() => {});
- const tableUpdateBody = {
- ...table,
- tn: table.table_name,
- originalColumns: table.columns.map((c) => ({
- ...c,
- cn: c.column_name,
- cno: c.column_name,
- })),
- columns: await Promise.all(
- table.columns.map(async (c) => {
- if (c.id === req.params.columnId) {
- const res = {
- ...c,
- ...column,
- cn: column.column_name,
- cno: c.column_name,
- dtxp: temp_dtxp,
- altered: Altered.UPDATE_COLUMN,
- };
- return Promise.resolve(res);
- } else {
- (c as any).cn = c.column_name;
- }
- return Promise.resolve(c);
- })
- ),
- };
+ await table.getColumns();
+ T.emit('evt', { evt_type: 'column:updated' });
+
+ return table;
+}
+
+export async function columnGet(param: { columnId: string }) {
+ return Column.get({ colId: param.columnId });
+}
+
+export async function columnSetAsPrimary(param: { columnId: string }) {
+ const column = await Column.get({ colId: param.columnId });
+ return Model.updatePrimaryColumn(column.fk_model_id, column.id);
+}
+
+export async function columnAdd(param: {
+ req?: any;
+ tableId: string;
+ column: ColumnReqType;
+}) {
+ validatePayload('swagger.json#/components/schemas/ColumnReq', param.column);
+
+ const table = await Model.getWithInfo({
+ id: param.tableId,
+ });
+
+ const base = await Base.get(table.base_id);
+
+ const project = await base.getProject();
+
+ if (param.column.title || param.column.column_name) {
+ const dbDriver = NcConnectionMgrv2.get(base);
+
+ const sqlClientType = dbDriver.clientType();
+
+ const mxColumnLength = Column.getMaxColumnNameLength(sqlClientType);
+
+ if (
+ (param.column.title || param.column.column_name).length > mxColumnLength
+ ) {
+ NcError.badRequest(
+ `Column name ${
+ param.column.title || param.column.column_name
+ } exceeds ${mxColumnLength} characters`
+ );
+ }
+ }
+
+ if (
+ !isVirtualCol(param.column) &&
+ !(await Column.checkTitleAvailable({
+ column_name: param.column.column_name,
+ fk_model_id: param.tableId,
+ }))
+ ) {
+ NcError.badRequest('Duplicate column name');
+ }
+ if (
+ !(await Column.checkAliasAvailable({
+ title: param.column.title || param.column.column_name,
+ fk_model_id: param.tableId,
+ }))
+ ) {
+ NcError.badRequest('Duplicate column alias');
+ }
+
+ let colBody: any = param.column;
+ switch (colBody.uidt) {
+ case UITypes.Rollup:
+ {
+ await validateRollupPayload(param.column);
+
+ await Column.insert({
+ ...colBody,
+ fk_model_id: table.id,
+ });
+ }
+ break;
+ case UITypes.Lookup:
+ {
+ await validateLookupPayload(param.column);
+
+ await Column.insert({
+ ...colBody,
+ fk_model_id: table.id,
+ });
+ }
+ break;
+
+ case UITypes.LinkToAnotherRecord:
+ await createLTARColumn({ ...param, base, project });
+ T.emit('evt', { evt_type: 'relation:created' });
+ break;
+
+ case UITypes.QrCode:
+ await Column.insert({
+ ...colBody,
+ fk_model_id: table.id,
+ });
+ break;
+ case UITypes.Barcode:
+ await Column.insert({
+ ...colBody,
+ fk_model_id: table.id,
+ });
+ break;
+ case UITypes.Formula:
+ colBody.formula = await substituteColumnAliasWithIdInFormula(
+ colBody.formula_raw || colBody.formula,
+ table.columns
+ );
+
+ try {
+ // test the query to see if it is valid in db level
+ const dbDriver = NcConnectionMgrv2.get(base);
+ await formulaQueryBuilderv2(colBody.formula, null, dbDriver, table);
+ } catch (e) {
+ console.error(e);
+ NcError.badRequest('Invalid Formula');
+ }
+
+ await Column.insert({
+ ...colBody,
+ fk_model_id: table.id,
+ });
- const sqlMgr = await ProjectMgrv2.getSqlMgr({
- id: base.project_id,
- });
- await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody);
+ break;
+ default:
+ {
+ colBody = getColumnPropsFromUIDT(colBody, base);
+ if (colBody.uidt === UITypes.Duration) {
+ colBody.dtxp = '20';
+ // by default, colBody.dtxs is 2
+ // Duration column needs more that that
+ colBody.dtxs = '4';
+ }
- await Column.update(req.params.columnId, {
- ...column,
- });
- }
+ if (
+ [UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt)
+ ) {
+ const dbDriver = NcConnectionMgrv2.get(base);
+ const driverType = dbDriver.clientType();
+ const optionTitles = colBody.colOptions.options.map((el) =>
+ el.title.replace(/'/g, "''")
+ );
- if (column.uidt === UITypes.SingleSelect) {
- if (driverType === 'mssql') {
- await dbDriver.raw(`UPDATE ?? SET ?? = ? WHERE ?? LIKE ?`, [
- table.table_name,
- column.column_name,
- newOp.title,
- column.column_name,
- option.title,
- ]);
+ // this is not used for select columns and cause issue for MySQL
+ colBody.dtxs = '';
+ // Handle default values
+ if (colBody.cdf) {
+ if (colBody.uidt === UITypes.SingleSelect) {
+ if (!optionTitles.includes(colBody.cdf.replace(/'/g, "''"))) {
+ NcError.badRequest(
+ `Default value '${colBody.cdf}' is not a select option.`
+ );
+ }
} else {
- await baseModel.bulkUpdateAll(
- { where: `(${column.title},eq,${option.title})` },
- { [column.column_name]: newOp.title },
- { cookie: req }
- );
+ for (const cdf of colBody.cdf.split(',')) {
+ if (!optionTitles.includes(cdf.replace(/'/g, "''"))) {
+ NcError.badRequest(
+ `Default value '${cdf}' is not a select option.`
+ );
+ }
+ }
}
- } else if (column.uidt === UITypes.MultiSelect) {
+
+ // handle single quote for default value
if (driverType === 'mysql' || driverType === 'mysql2') {
- if (colBody.dt === 'set') {
- await dbDriver.raw(
- `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), CONCAT(',', ?, ','))) WHERE FIND_IN_SET(?, ??)`,
- [
- table.table_name,
- column.column_name,
- column.column_name,
- option.title,
- newOp.title,
- option.title,
- column.column_name,
- ]
- );
- } else {
- await dbDriver.raw(
- `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), CONCAT(',', ?, ',')))`,
- [
- table.table_name,
- column.column_name,
- column.column_name,
- option.title,
- newOp.title,
- ]
- );
- }
- } else if (driverType === 'pg') {
- await dbDriver.raw(
- `UPDATE ?? SET ?? = array_to_string(array_replace(string_to_array(??, ','), ?, ?), ',')`,
- [
- table.table_name,
- column.column_name,
- column.column_name,
- option.title,
- newOp.title,
- ]
- );
- } else if (driverType === 'mssql') {
- await dbDriver.raw(
- `UPDATE ?? SET ?? = substring(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ',')), 2, len(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ','))) - 2)`,
- [
- table.table_name,
- column.column_name,
- column.column_name,
- option.title,
- newOp.title,
- column.column_name,
- option.title,
- newOp.title,
- ]
- );
- } else if (driverType === 'sqlite3') {
- await dbDriver.raw(
- `UPDATE ?? SET ?? = TRIM(REPLACE(',' || ?? || ',', ',' || ? || ',', ',' || ? || ','), ',')`,
- [
- table.table_name,
- column.column_name,
- column.column_name,
- option.title,
- newOp.title,
- ]
- );
+ colBody.cdf = colBody.cdf.replace(/'/g, "'");
+ } else {
+ colBody.cdf = colBody.cdf.replace(/'/g, "''");
+ }
+
+ if (driverType === 'pg') {
+ colBody.cdf = `'${colBody.cdf}'`;
}
}
- }
- }
- for (const ch of interchange) {
- const newOp = ch.def_option;
- if (column.uidt === UITypes.SingleSelect) {
- if (driverType === 'mssql') {
- await dbDriver.raw(`UPDATE ?? SET ?? = ? WHERE ?? LIKE ?`, [
- table.table_name,
- column.column_name,
- newOp.title,
- column.column_name,
- ch.temp_title,
- ]);
- } else {
- await baseModel.bulkUpdateAll(
- { where: `(${column.title},eq,${ch.temp_title})` },
- { [column.column_name]: newOp.title },
- { cookie: req }
- );
+ // Restrict duplicates
+ const titles = colBody.colOptions.options.map((el) => el.title);
+ if (
+ titles.some(function (item) {
+ return titles.indexOf(item) !== titles.lastIndexOf(item);
+ })
+ ) {
+ NcError.badRequest('Duplicates are not allowed!');
}
- } else if (column.uidt === UITypes.MultiSelect) {
- if (driverType === 'mysql' || driverType === 'mysql2') {
- if (colBody.dt === 'set') {
- await dbDriver.raw(
- `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), CONCAT(',', ?, ','))) WHERE FIND_IN_SET(?, ??)`,
- [
- table.table_name,
- column.column_name,
- column.column_name,
- ch.temp_title,
- newOp.title,
- ch.temp_title,
- column.column_name,
- ]
- );
- } else {
- await dbDriver.raw(
- `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), CONCAT(',', ?, ',')))`,
- [
- table.table_name,
- column.column_name,
- column.column_name,
- ch.temp_title,
- newOp.title,
- ch.temp_title,
- column.column_name,
- ]
- );
+
+ // Restrict empty options
+ if (
+ titles.some(function (item) {
+ return item === '';
+ })
+ ) {
+ NcError.badRequest('Empty options are not allowed!');
+ }
+
+ // Trim end of enum/set
+ if (colBody.dt === 'enum' || colBody.dt === 'set') {
+ for (const opt of colBody.colOptions.options) {
+ opt.title = opt.title.trimEnd();
}
- } else if (driverType === 'pg') {
- await dbDriver.raw(
- `UPDATE ?? SET ?? = array_to_string(array_replace(string_to_array(??, ','), ?, ?), ',')`,
- [
- table.table_name,
- column.column_name,
- column.column_name,
- ch.temp_title,
- newOp.title,
- ]
- );
- } else if (driverType === 'mssql') {
- await dbDriver.raw(
- `UPDATE ?? SET ?? = substring(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ',')), 2, len(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ','))) - 2)`,
- [
- table.table_name,
- column.column_name,
- column.column_name,
- ch.temp_title,
- newOp.title,
- column.column_name,
- ch.temp_title,
- newOp.title,
- ]
- );
- } else if (driverType === 'sqlite3') {
- await dbDriver.raw(
- `UPDATE ?? SET ?? = TRIM(REPLACE(',' || ?? || ',', ',' || ? || ',', ',' || ? || ','), ',')`,
- [
- table.table_name,
- column.column_name,
- column.column_name,
- ch.temp_title,
- newOp.title,
- ]
- );
}
- }
- }
- }
- const tableUpdateBody = {
- ...table,
- tn: table.table_name,
- originalColumns: table.columns.map((c) => ({
- ...c,
- cn: c.column_name,
- cno: c.column_name,
- })),
- columns: await Promise.all(
- table.columns.map(async (c) => {
- if (c.id === req.params.columnId) {
- const res = {
- ...c,
- ...colBody,
- cn: colBody.column_name,
- cno: c.column_name,
- altered: Altered.UPDATE_COLUMN,
- };
+ if (colBody.uidt === UITypes.SingleSelect) {
+ colBody.dtxp = colBody.colOptions?.options.length
+ ? `${colBody.colOptions.options
+ .map((o) => `'${o.title.replace(/'/gi, "''")}'`)
+ .join(',')}`
+ : '';
+ } else if (colBody.uidt === UITypes.MultiSelect) {
+ colBody.dtxp = colBody.colOptions?.options.length
+ ? `${colBody.colOptions.options
+ .map((o) => {
+ if (o.title.includes(',')) {
+ NcError.badRequest("Illegal char(',') for MultiSelect");
+ }
+ return `'${o.title.replace(/'/gi, "''")}'`;
+ })
+ .join(',')}`
+ : '';
+ }
+
+ // Handle empty enum/set for mysql (we restrict empty user options beforehand)
+ if (driverType === 'mysql' || driverType === 'mysql2') {
+ if (
+ !colBody.colOptions.options.length &&
+ (!colBody.dtxp || colBody.dtxp === '')
+ ) {
+ colBody.dtxp = "''";
+ }
- // update formula with new column name
- if (c.column_name != colBody.column_name) {
- const formulas = await Noco.ncMeta
- .knex(MetaTable.COL_FORMULA)
- .where('formula', 'like', `%${c.id}%`);
- if (formulas) {
- const new_column = c;
- new_column.column_name = colBody.column_name;
- new_column.title = colBody.title;
- for (const f of formulas) {
- // the formula with column IDs only
- const formula = f.formula;
- // replace column IDs with alias to get the new formula_raw
- const new_formula_raw = substituteColumnIdWithAliasInFormula(
- formula,
- [new_column]
- );
- await FormulaColumn.update(c.id, {
- formula_raw: new_formula_raw,
- });
- }
+ if (colBody.dt === 'set') {
+ if (colBody.colOptions?.options.length > 64) {
+ colBody.dt = 'text';
}
}
- return Promise.resolve(res);
- } else {
- (c as any).cn = c.column_name;
}
- return Promise.resolve(c);
- })
- ),
- };
-
- const sqlMgr = await ProjectMgrv2.getSqlMgr({ id: base.project_id });
- await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody);
+ }
- await Column.update(req.params.columnId, {
- ...colBody,
- });
- } else {
- colBody = getColumnPropsFromUIDT(colBody, base);
- const tableUpdateBody = {
- ...table,
- tn: table.table_name,
- originalColumns: table.columns.map((c) => ({
- ...c,
- cn: c.column_name,
- cno: c.column_name,
- })),
- columns: await Promise.all(
- table.columns.map(async (c) => {
- if (c.id === req.params.columnId) {
- const res = {
- ...c,
+ const tableUpdateBody = {
+ ...table,
+ tn: table.table_name,
+ originalColumns: table.columns.map((c) => ({
+ ...c,
+ cn: c.column_name,
+ })),
+ columns: [
+ ...table.columns.map((c) => ({ ...c, cn: c.column_name })),
+ {
...colBody,
cn: colBody.column_name,
- cno: c.column_name,
- altered: Altered.UPDATE_COLUMN,
- };
+ altered: Altered.NEW_COLUMN,
+ },
+ ],
+ };
- // update formula with new column name
- if (c.column_name != colBody.column_name) {
- const formulas = await Noco.ncMeta
- .knex(MetaTable.COL_FORMULA)
- .where('formula', 'like', `%${c.id}%`);
- if (formulas) {
- const new_column = c;
- new_column.column_name = colBody.column_name;
- new_column.title = colBody.title;
- for (const f of formulas) {
- // the formula with column IDs only
- const formula = f.formula;
- // replace column IDs with alias to get the new formula_raw
- const new_formula_raw = substituteColumnIdWithAliasInFormula(
- formula,
- [new_column]
- );
- await FormulaColumn.update(c.id, {
- formula_raw: new_formula_raw,
- });
- }
- }
- }
- return Promise.resolve(res);
- } else {
- (c as any).cn = c.column_name;
+ const sqlClient = await NcConnectionMgrv2.getSqlClient(base);
+ const sqlMgr = await ProjectMgrv2.getSqlMgr({ id: base.project_id });
+ await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody);
+
+ const columns: Array<
+ Omit & {
+ cn: string;
+ system?: boolean;
}
- return Promise.resolve(c);
- })
- ),
- };
+ > = (await sqlClient.columnList({ tn: table.table_name }))?.data?.list;
- const sqlMgr = await ProjectMgrv2.getSqlMgr({ id: base.project_id });
- await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody);
+ const insertedColumnMeta =
+ columns.find((c) => c.cn === colBody.column_name) || ({} as any);
- await Column.update(req.params.columnId, {
- ...colBody,
- });
+ await Column.insert({
+ ...colBody,
+ ...insertedColumnMeta,
+ dtxp: [UITypes.MultiSelect, UITypes.SingleSelect].includes(
+ colBody.uidt as any
+ )
+ ? colBody.dtxp
+ : insertedColumnMeta.dtxp,
+ fk_model_id: table.id,
+ });
+ }
+ break;
}
+
+ await table.getColumns();
+
await Audit.insert({
project_id: base.project_id,
op_type: AuditOperationTypes.TABLE_COLUMN,
- op_sub_type: AuditOperationSubTypes.UPDATED,
- user: (req as any)?.user?.email,
- description: `updated column ${column.column_name} with alias ${column.title} from table ${table.table_name}`,
- ip: (req as any).clientIp,
+ op_sub_type: AuditOperationSubTypes.CREATED,
+ user: param?.req?.user?.email,
+ description: `created column ${colBody.column_name} with alias ${colBody.title} from table ${table.table_name}`,
+ ip: param?.req.clientIp,
}).then(() => {});
- await table.getColumns();
- Tele.emit('evt', { evt_type: 'column:updated' });
+ T.emit('evt', { evt_type: 'column:created' });
- res.json(table);
+ return table;
}
-export async function columnDelete(req: Request, res: Response) {
- const column = await Column.get({ colId: req.params.columnId });
+export async function columnDelete(param: { req?: any; columnId: string }) {
+ const column = await Column.get({ colId: param.columnId });
const table = await Model.getWithInfo({
id: column.fk_model_id,
});
@@ -1437,7 +1141,7 @@ export async function columnDelete(req: Request, res: Response) {
case UITypes.QrCode:
case UITypes.Barcode:
case UITypes.Formula:
- await Column.delete(req.params.columnId);
+ await Column.delete(param.columnId);
break;
case UITypes.LinkToAnotherRecord:
{
@@ -1566,7 +1270,7 @@ export async function columnDelete(req: Request, res: Response) {
break;
}
}
- Tele.emit('evt', { evt_type: 'raltion:deleted' });
+ T.emit('evt', { evt_type: 'raltion:deleted' });
break;
case UITypes.ForeignKey: {
NcError.notImplemented();
@@ -1593,7 +1297,7 @@ export async function columnDelete(req: Request, res: Response) {
cno: c.column_name,
})),
columns: table.columns.map((c) => {
- if (c.id === req.params.columnId) {
+ if (c.id === param.columnId) {
return {
...c,
cn: c.column_name,
@@ -1609,7 +1313,7 @@ export async function columnDelete(req: Request, res: Response) {
await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody);
- await Column.delete(req.params.columnId);
+ await Column.delete(param.columnId);
}
}
@@ -1617,9 +1321,9 @@ export async function columnDelete(req: Request, res: Response) {
project_id: base.project_id,
op_type: AuditOperationTypes.TABLE_COLUMN,
op_sub_type: AuditOperationSubTypes.DELETED,
- user: (req as any)?.user?.email,
+ user: param?.req?.user?.email,
description: `deleted column ${column.column_name} with alias ${column.title} from table ${table.table_name}`,
- ip: (req as any).clientIp,
+ ip: param?.req.clientIp,
}).then(() => {});
await table.getColumns();
@@ -1632,16 +1336,9 @@ export async function columnDelete(req: Request, res: Response) {
);
}
- // await ncMeta.commit();
- // await sql-mgr.commit();
- Tele.emit('evt', { evt_type: 'column:deleted' });
+ T.emit('evt', { evt_type: 'column:deleted' });
- res.json(table);
- // } catch (e) {
- // sql-mgr.rollback();
- // ncMeta.rollback();
- // throw e;
- // }
+ return table;
}
const deleteHmOrBtRelation = async (
@@ -1758,7 +1455,298 @@ const deleteHmOrBtRelation = async (
await Column.delete(childColumn.id, ncMeta);
};
-async function createColumnIndex({
+async function createLTARColumn(param: {
+ tableId: string;
+ column: ColumnReqType;
+ base: Base;
+ project: Project;
+}) {
+ validateParams(['parentId', 'childId', 'type'], param.column);
+
+ // get parent and child models
+ const parent = await Model.getWithInfo({
+ id: (param.column as LinkToAnotherColumnReqType).parentId,
+ });
+ const child = await Model.getWithInfo({
+ id: (param.column as LinkToAnotherColumnReqType).childId,
+ });
+ let childColumn: Column;
+
+ const sqlMgr = await ProjectMgrv2.getSqlMgr({
+ id: param.base.project_id,
+ });
+ if (
+ (param.column as LinkToAnotherColumnReqType).type === 'hm' ||
+ (param.column as LinkToAnotherColumnReqType).type === 'bt'
+ ) {
+ // populate fk column name
+ const fkColName = getUniqueColumnName(
+ await child.getColumns(),
+ `${parent.table_name}_id`
+ );
+
+ let foreignKeyName;
+ {
+ // create foreign key
+ const newColumn = {
+ cn: fkColName,
+
+ title: fkColName,
+ column_name: fkColName,
+ rqd: false,
+ pk: false,
+ ai: false,
+ cdf: null,
+ dt: parent.primaryKey.dt,
+ dtxp: parent.primaryKey.dtxp,
+ dtxs: parent.primaryKey.dtxs,
+ un: parent.primaryKey.un,
+ altered: Altered.NEW_COLUMN,
+ };
+ const tableUpdateBody = {
+ ...child,
+ tn: child.table_name,
+ originalColumns: child.columns.map((c) => ({
+ ...c,
+ cn: c.column_name,
+ })),
+ columns: [
+ ...child.columns.map((c) => ({
+ ...c,
+ cn: c.column_name,
+ })),
+ newColumn,
+ ],
+ };
+
+ await sqlMgr.sqlOpPlus(param.base, 'tableUpdate', tableUpdateBody);
+
+ const { id } = await Column.insert({
+ ...newColumn,
+ uidt: UITypes.ForeignKey,
+ fk_model_id: child.id,
+ });
+
+ childColumn = await Column.get({ colId: id });
+
+ // ignore relation creation if virtual
+ if (!(param.column as LinkToAnotherColumnReqType).virtual) {
+ foreignKeyName = generateFkName(parent, child);
+ // create relation
+ await sqlMgr.sqlOpPlus(param.base, 'relationCreate', {
+ childColumn: fkColName,
+ childTable: child.table_name,
+ parentTable: parent.table_name,
+ onDelete: 'NO ACTION',
+ onUpdate: 'NO ACTION',
+ type: 'real',
+ parentColumn: parent.primaryKey.column_name,
+ foreignKeyName,
+ });
+ }
+
+ // todo: create index for virtual relations as well
+ // create index for foreign key in pg
+ if (param.base.type === 'pg') {
+ await createColumnIndex({
+ column: new Column({
+ ...newColumn,
+ fk_model_id: child.id,
+ }),
+ base: param.base,
+ sqlMgr,
+ });
+ }
+ }
+ await createHmAndBtColumn(
+ child,
+ parent,
+ childColumn,
+ (param.column as LinkToAnotherColumnReqType).type as RelationTypes,
+ (param.column as LinkToAnotherColumnReqType).title,
+ foreignKeyName,
+ (param.column as LinkToAnotherColumnReqType).virtual
+ );
+ } else if ((param.column as LinkToAnotherColumnReqType).type === 'mm') {
+ const aTn = `${param.project?.prefix ?? ''}_nc_m2m_${randomID()}`;
+ const aTnAlias = aTn;
+
+ const parentPK = parent.primaryKey;
+ const childPK = child.primaryKey;
+
+ const associateTableCols = [];
+
+ const parentCn = 'table1_id';
+ const childCn = 'table2_id';
+
+ associateTableCols.push(
+ {
+ cn: childCn,
+ column_name: childCn,
+ title: childCn,
+ rqd: true,
+ pk: true,
+ ai: false,
+ cdf: null,
+ dt: childPK.dt,
+ dtxp: childPK.dtxp,
+ dtxs: childPK.dtxs,
+ un: childPK.un,
+ altered: 1,
+ uidt: UITypes.ForeignKey,
+ },
+ {
+ cn: parentCn,
+ column_name: parentCn,
+ title: parentCn,
+ rqd: true,
+ pk: true,
+ ai: false,
+ cdf: null,
+ dt: parentPK.dt,
+ dtxp: parentPK.dtxp,
+ dtxs: parentPK.dtxs,
+ un: parentPK.un,
+ altered: 1,
+ uidt: UITypes.ForeignKey,
+ }
+ );
+
+ await sqlMgr.sqlOpPlus(param.base, 'tableCreate', {
+ tn: aTn,
+ _tn: aTnAlias,
+ columns: associateTableCols,
+ });
+
+ const assocModel = await Model.insert(param.project.id, param.base.id, {
+ table_name: aTn,
+ title: aTnAlias,
+ // todo: sanitize
+ mm: true,
+ columns: associateTableCols,
+ });
+
+ let foreignKeyName1;
+ let foreignKeyName2;
+
+ if (!(param.column as LinkToAnotherColumnReqType).virtual) {
+ foreignKeyName1 = generateFkName(parent, child);
+ foreignKeyName2 = generateFkName(parent, child);
+
+ const rel1Args = {
+ ...param.column,
+ childTable: aTn,
+ childColumn: parentCn,
+ parentTable: parent.table_name,
+ parentColumn: parentPK.column_name,
+ type: 'real',
+ foreignKeyName: foreignKeyName1,
+ };
+ const rel2Args = {
+ ...param.column,
+ childTable: aTn,
+ childColumn: childCn,
+ parentTable: child.table_name,
+ parentColumn: childPK.column_name,
+ type: 'real',
+ foreignKeyName: foreignKeyName2,
+ };
+
+ await sqlMgr.sqlOpPlus(param.base, 'relationCreate', rel1Args);
+ await sqlMgr.sqlOpPlus(param.base, 'relationCreate', rel2Args);
+ }
+ const parentCol = (await assocModel.getColumns())?.find(
+ (c) => c.column_name === parentCn
+ );
+ const childCol = (await assocModel.getColumns())?.find(
+ (c) => c.column_name === childCn
+ );
+
+ await createHmAndBtColumn(
+ assocModel,
+ child,
+ childCol,
+ null,
+ null,
+ foreignKeyName1,
+ (param.column as LinkToAnotherColumnReqType).virtual,
+ true
+ );
+ await createHmAndBtColumn(
+ assocModel,
+ parent,
+ parentCol,
+ null,
+ null,
+ foreignKeyName2,
+ (param.column as LinkToAnotherColumnReqType).virtual,
+ true
+ );
+
+ await Column.insert({
+ title: getUniqueColumnAliasName(
+ await child.getColumns(),
+ `${parent.title} List`
+ ),
+ uidt: UITypes.LinkToAnotherRecord,
+ type: 'mm',
+
+ // ref_db_alias
+ fk_model_id: child.id,
+ // db_type:
+
+ fk_child_column_id: childPK.id,
+ fk_parent_column_id: parentPK.id,
+
+ fk_mm_model_id: assocModel.id,
+ fk_mm_child_column_id: childCol.id,
+ fk_mm_parent_column_id: parentCol.id,
+ fk_related_model_id: parent.id,
+ });
+ await Column.insert({
+ title: getUniqueColumnAliasName(
+ await parent.getColumns(),
+ param.column.title ?? `${child.title} List`
+ ),
+
+ uidt: UITypes.LinkToAnotherRecord,
+ type: 'mm',
+
+ fk_model_id: parent.id,
+
+ fk_child_column_id: parentPK.id,
+ fk_parent_column_id: childPK.id,
+
+ fk_mm_model_id: assocModel.id,
+ fk_mm_child_column_id: parentCol.id,
+ fk_mm_parent_column_id: childCol.id,
+ fk_related_model_id: child.id,
+ });
+
+ // todo: create index for virtual relations as well
+ // create index for foreign key in pg
+ if (param.base.type === 'pg') {
+ await createColumnIndex({
+ column: new Column({
+ ...associateTableCols[0],
+ fk_model_id: assocModel.id,
+ }),
+ base: param.base,
+ sqlMgr,
+ });
+ await createColumnIndex({
+ column: new Column({
+ ...associateTableCols[1],
+ fk_model_id: assocModel.id,
+ }),
+ base: param.base,
+ sqlMgr,
+ });
+ }
+ }
+}
+
+export async function createColumnIndex({
column,
sqlMgr,
base,
@@ -1781,36 +1769,25 @@ async function createColumnIndex({
sqlMgr.sqlOpPlus(base, 'indexCreate', indexArgs);
}
-const router = Router({ mergeParams: true });
-
-router.post(
- '/api/v1/db/meta/tables/:tableId/columns/',
- metaApiMetrics,
- getAjvValidatorMw('swagger.json#/components/schemas/ColumnReq'),
- ncMetaAclMw(columnAdd, 'columnAdd')
-);
-
-router.patch(
- '/api/v1/db/meta/columns/:columnId',
- metaApiMetrics,
- ncMetaAclMw(columnUpdate, 'columnUpdate')
-);
-
-router.delete(
- '/api/v1/db/meta/columns/:columnId',
- metaApiMetrics,
- ncMetaAclMw(columnDelete, 'columnDelete')
-);
-
-router.get(
- '/api/v1/db/meta/columns/:columnId',
- metaApiMetrics,
- ncMetaAclMw(columnGet, 'columnGet')
-);
-
-router.post(
- '/api/v1/db/meta/columns/:columnId/primary',
- metaApiMetrics,
- ncMetaAclMw(columnSetAsPrimary, 'columnSetAsPrimary')
-);
-export default router;
+async function updateRollupOrLookup(colBody: any, column: Column) {
+ if (
+ UITypes.Lookup === column.uidt &&
+ validateRequiredField(colBody, [
+ 'fk_lookup_column_id',
+ 'fk_relation_column_id',
+ ])
+ ) {
+ await validateLookupPayload(colBody, column.id);
+ await Column.update(column.id, colBody);
+ } else if (
+ UITypes.Rollup === column.uidt &&
+ validateRequiredField(colBody, [
+ 'fk_relation_column_id',
+ 'fk_rollup_column_id',
+ 'rollup_function',
+ ])
+ ) {
+ await validateRollupPayload(colBody);
+ await Column.update(column.id, colBody);
+ }
+}
diff --git a/packages/nocodb/src/lib/services/dataService/bulkData.ts b/packages/nocodb/src/lib/services/dataService/bulkData.ts
new file mode 100644
index 0000000000..2ac6b5a20b
--- /dev/null
+++ b/packages/nocodb/src/lib/services/dataService/bulkData.ts
@@ -0,0 +1,102 @@
+import { BaseModelSqlv2 } from '../../db/sql-data-mapper/lib/sql/BaseModelSqlv2';
+import { Base, Model } from '../../models';
+import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
+import { getViewAndModelByAliasOrId, PathParams } from './helpers';
+
+type BulkOperation =
+ | 'bulkInsert'
+ | 'bulkUpdate'
+ | 'bulkUpdateAll'
+ | 'bulkDelete'
+ | 'bulkDeleteAll';
+
+export async function getModelViewBase(param: PathParams) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+
+ const base = await Base.get(model.base_id);
+ return { model, view, base };
+}
+
+export async function executeBulkOperation(
+ param: PathParams & {
+ operation: T;
+ options: Parameters;
+ }
+) {
+ const { model, view, base } = await getModelViewBase(param);
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+ return await baseModel[param.operation].apply(null, param.options);
+}
+
+// todo: Integrate with filterArrJson bulkDataUpdateAll
+export async function bulkDataInsert(
+ param: PathParams & {
+ body: any;
+ cookie: any;
+ }
+) {
+ return await executeBulkOperation({
+ ...param,
+ operation: 'bulkInsert',
+ options: [param.body, { cookie: param.cookie }],
+ });
+}
+
+// todo: Integrate with filterArrJson bulkDataUpdateAll
+export async function bulkDataUpdate(
+ param: PathParams & {
+ body: any;
+ cookie: any;
+ }
+) {
+ return await executeBulkOperation({
+ ...param,
+ operation: 'bulkUpdate',
+ options: [param.body, { cookie: param.cookie }],
+ });
+}
+
+// todo: Integrate with filterArrJson bulkDataUpdateAll
+export async function bulkDataUpdateAll(
+ param: PathParams & {
+ body: any;
+ cookie: any;
+ query: any;
+ }
+) {
+ return await executeBulkOperation({
+ ...param,
+ operation: 'bulkUpdateAll',
+ options: [param.query, param.body, { cookie: param.cookie }],
+ });
+}
+
+export async function bulkDataDelete(
+ param: PathParams & {
+ body: any;
+ cookie: any;
+ }
+) {
+ return await executeBulkOperation({
+ ...param,
+ operation: 'bulkDelete',
+ options: [param.body, { cookie: param.cookie }],
+ });
+}
+
+// todo: Integrate with filterArrJson bulkDataDeleteAll
+export async function bulkDataDeleteAll(
+ param: PathParams & {
+ query: any;
+ }
+) {
+ return await executeBulkOperation({
+ ...param,
+ operation: 'bulkDeleteAll',
+ options: [param.query],
+ });
+}
diff --git a/packages/nocodb/src/lib/services/dataService/dataAliasNestedService.ts b/packages/nocodb/src/lib/services/dataService/dataAliasNestedService.ts
new file mode 100644
index 0000000000..20959d15fa
--- /dev/null
+++ b/packages/nocodb/src/lib/services/dataService/dataAliasNestedService.ts
@@ -0,0 +1,280 @@
+import { PagedResponseImpl } from '../../meta/helpers/PagedResponse';
+import { Base, Model } from '../../models';
+import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
+import {
+ getColumnByIdOrName,
+ getViewAndModelByAliasOrId,
+ PathParams,
+} from './helpers';
+import { NcError } from '../../meta/helpers/catchError';
+
+// todo: handle case where the given column is not ltar
+export async function mmList(
+ param: PathParams & {
+ query: any;
+ columnName: string;
+ rowId: string;
+ }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ const column = await getColumnByIdOrName(param.columnName, model);
+
+ const data = await baseModel.mmList(
+ {
+ colId: column.id,
+ parentId: param.rowId,
+ },
+ param.query as any
+ );
+ const count: any = await baseModel.mmListCount({
+ colId: column.id,
+ parentId: param.rowId,
+ });
+
+ return new PagedResponseImpl(data, {
+ count,
+ ...param.query,
+ });
+}
+
+export async function mmExcludedList(
+ param: PathParams & {
+ query: any;
+ columnName: string;
+ rowId: string;
+ }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+ const column = await getColumnByIdOrName(param.columnName, model);
+
+ const data = await baseModel.getMmChildrenExcludedList(
+ {
+ colId: column.id,
+ pid: param.rowId,
+ },
+ param.query
+ );
+
+ const count = await baseModel.getMmChildrenExcludedListCount(
+ {
+ colId: column.id,
+ pid: param.rowId,
+ },
+ param.query
+ );
+
+ return new PagedResponseImpl(data, {
+ count,
+ ...param.query,
+ });
+}
+
+export async function hmExcludedList(
+ param: PathParams & {
+ query: any;
+ columnName: string;
+ rowId: string;
+ }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ const column = await getColumnByIdOrName(param.columnName, model);
+
+ const data = await baseModel.getHmChildrenExcludedList(
+ {
+ colId: column.id,
+ pid: param.rowId,
+ },
+ param.query
+ );
+
+ const count = await baseModel.getHmChildrenExcludedListCount(
+ {
+ colId: column.id,
+ pid: param.rowId,
+ },
+ param.query
+ );
+
+ return new PagedResponseImpl(data, {
+ count,
+ ...param.query,
+ });
+}
+
+export async function btExcludedList(
+ param: PathParams & {
+ query: any;
+ columnName: string;
+ rowId: string;
+ }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ const column = await getColumnByIdOrName(param.columnName, model);
+
+ const data = await baseModel.getBtChildrenExcludedList(
+ {
+ colId: column.id,
+ cid: param.rowId,
+ },
+ param.query
+ );
+
+ const count = await baseModel.getBtChildrenExcludedListCount(
+ {
+ colId: column.id,
+ cid: param.rowId,
+ },
+ param.query
+ );
+
+ return new PagedResponseImpl(data, {
+ count,
+ ...param.query,
+ });
+}
+
+// todo: handle case where the given column is not ltar
+export async function hmList(
+ param: PathParams & {
+ query: any;
+ columnName: string;
+ rowId: string;
+ }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ const column = await getColumnByIdOrName(param.columnName, model);
+
+ const data = await baseModel.hmList(
+ {
+ colId: column.id,
+ id: param.rowId,
+ },
+ param.query
+ );
+
+ const count = await baseModel.hmListCount({
+ colId: column.id,
+ id: param.rowId,
+ });
+
+ return new PagedResponseImpl(data, {
+ count,
+ ...param.query,
+ } as any);
+}
+
+//@ts-ignore
+export async function relationDataRemove(
+ param: PathParams & {
+ columnName: string;
+ rowId: string;
+ refRowId: string;
+ cookie: any;
+ }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ const column = await getColumnByIdOrName(param.columnName, model);
+
+ await baseModel.removeChild({
+ colId: column.id,
+ childId: param.refRowId,
+ rowId: param.rowId,
+ cookie: param.cookie,
+ });
+
+ return true;
+}
+
+//@ts-ignore
+// todo: Give proper error message when reference row is already related and handle duplicate ref row id in hm
+export async function relationDataAdd(
+ param: PathParams & {
+ columnName: string;
+ rowId: string;
+ refRowId: string;
+ cookie: any;
+ }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ const column = await getColumnByIdOrName(param.columnName, model);
+ await baseModel.addChild({
+ colId: column.id,
+ childId: param.refRowId,
+ rowId: param.rowId,
+ cookie: param.cookie,
+ });
+
+ return true;
+}
diff --git a/packages/nocodb/src/lib/services/dataService/export.ts b/packages/nocodb/src/lib/services/dataService/export.ts
new file mode 100644
index 0000000000..fa222f61ce
--- /dev/null
+++ b/packages/nocodb/src/lib/services/dataService/export.ts
@@ -0,0 +1,50 @@
+import { getViewAndModelByAliasOrId, PathParams } from './helpers';
+import { View } from '../../models';
+
+// Todo: bring logic from controller
+export async function excelDataExport(
+ param: PathParams & {
+ query: any;
+ }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+ let targetView = view;
+ if (!targetView) {
+ targetView = await View.getDefaultView(model.id);
+ }
+ // const { offset, elapsed, data } = await extractXlsxData({
+ // view: targetView,
+ // query: param.query,
+ // });
+ // const wb = XLSX.utils.book_new();
+ // XLSX.utils.book_append_sheet(wb, data, targetView.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(
+ // targetView.title
+ // )}-export.xlsx"`,
+ // });
+ // res.end(buf);
+}
+
+// async function csvDataExport(req: Request, res: Response) {
+// const { model, view } = await getViewAndModelFromRequestByAliasOrId(req);
+// let targetView = view;
+// if (!targetView) {
+// targetView = await View.getDefaultView(model.id);
+// }
+// const { offset, elapsed, data } = await extractCsvData(targetView, req);
+//
+// res.set({
+// 'Access-Control-Expose-Headers': 'nc-export-offset',
+// 'nc-export-offset': offset,
+// 'nc-export-elapsed-time': elapsed,
+// 'Content-Disposition': `attachment; filename="${encodeURI(
+// targetView.title
+// )}-export.csv"`,
+// });
+// res.send(data);
+// }
diff --git a/packages/nocodb/src/lib/meta/api/dataApis/helpers.ts b/packages/nocodb/src/lib/services/dataService/helpers.ts
similarity index 78%
rename from packages/nocodb/src/lib/meta/api/dataApis/helpers.ts
rename to packages/nocodb/src/lib/services/dataService/helpers.ts
index aec0e4216e..9654fc3dfc 100644
--- a/packages/nocodb/src/lib/meta/api/dataApis/helpers.ts
+++ b/packages/nocodb/src/lib/services/dataService/helpers.ts
@@ -1,35 +1,40 @@
-import Project from '../../../models/Project';
-import Model from '../../../models/Model';
-import View from '../../../models/View';
-import { NcError } from '../../helpers/catchError';
import { Request } from 'express';
-import Base from '../../../models/Base';
-import NcConnectionMgrv2 from '../../../utils/common/NcConnectionMgrv2';
-import { isSystemColumn, UITypes } from 'nocodb-sdk';
-
import { nocoExecute } from 'nc-help';
+import { isSystemColumn, UITypes } from 'nocodb-sdk';
import * as XLSX from 'xlsx';
-import Column from '../../../models/Column';
-import LookupColumn from '../../../models/LookupColumn';
-import LinkToAnotherRecordColumn from '../../../models/LinkToAnotherRecordColumn';
-
+import { BaseModelSqlv2 } from '../../db/sql-data-mapper/lib/sql/BaseModelSqlv2';
+import getAst from '../../db/sql-data-mapper/lib/sql/helpers/getAst';
+import { NcError } from '../../meta/helpers/catchError';
+import { Model, View } from '../../models';
+import Base from '../../models/Base';
+import Column from '../../models/Column';
+import LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn';
+import LookupColumn from '../../models/LookupColumn';
+import Project from '../../models/Project';
+import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
import papaparse from 'papaparse';
-import getAst from '../../../db/sql-data-mapper/lib/sql/helpers/getAst';
-export async function getViewAndModelFromRequestByAliasOrId(
- req:
- | Request<{ projectName: string; tableName: string; viewName?: string }>
- | Request
-) {
- const project = await Project.getWithInfoByTitleOrId(req.params.projectName);
+
+export interface PathParams {
+ projectName: string;
+ tableName: string;
+ viewName?: string;
+}
+
+export async function getViewAndModelByAliasOrId(param: {
+ projectName: string;
+ tableName: string;
+ viewName?: string;
+}) {
+ const project = await Project.getWithInfoByTitleOrId(param.projectName);
const model = await Model.getByAliasOrId({
project_id: project.id,
- aliasOrId: req.params.tableName,
+ aliasOrId: param.tableName,
});
const view =
- req.params.viewName &&
+ param.viewName &&
(await View.getByTitleOrId({
- titleOrId: req.params.viewName,
+ titleOrId: param.viewName,
fk_model_id: model.id,
}));
if (!model) NcError.notFound('Table not found');
@@ -56,7 +61,12 @@ export async function extractXlsxData(view: View, req: Request) {
dbDriver: NcConnectionMgrv2.get(base),
});
- const { offset, dbRows, elapsed } = await getDbRows(baseModel, view, req);
+ const { offset, dbRows, elapsed } = await getDbRows({
+ baseModel,
+ view,
+ siteUrl: (req as any).ncSiteUrl,
+ query: req.query,
+ });
const fields = req.query.fields as string[];
@@ -86,7 +96,12 @@ export async function extractCsvData(view: View, req: Request) {
dbDriver: NcConnectionMgrv2.get(base),
});
- const { offset, dbRows, elapsed } = await getDbRows(baseModel, view, req);
+ const { offset, dbRows, elapsed } = await getDbRows({
+ baseModel,
+ view,
+ query: req.query,
+ siteUrl: (req as any).ncSiteUrl,
+ });
const data = papaparse.unparse(
{
@@ -111,64 +126,6 @@ export async function extractCsvData(view: View, req: Request) {
return { offset, dbRows, elapsed, data };
}
-async function getDbRows(baseModel, view: View, req: Request) {
- 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;
-
- const listArgs: any = { ...req.query };
- try {
- listArgs.filterArr = JSON.parse(listArgs.filterArrJson);
- } catch (e) {}
- try {
- listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
- } catch (e) {}
-
- for (
- elapsed = 0;
- elapsed < timeout;
- offset += limit,
- temp = process.hrtime(startTime),
- elapsed = temp[0] * 1000 + temp[1] / 1000000
- ) {
- const rows = await nocoExecute(
- await getAst({
- query: req.query,
- includePkByDefault: false,
- model: view.model,
- view,
- }),
- await baseModel.list({ ...listArgs, offset, limit }),
- {},
- req.query
- );
-
- if (!rows?.length) {
- offset = -1;
- break;
- }
-
- for (const row of rows) {
- const dbRow = { ...row };
-
- for (const column of view.model.columns) {
- if (isSystemColumn(column) && !view.show_system_fields) continue;
- dbRow[column.title] = await serializeCellValue({
- value: row[column.title],
- column,
- siteUrl: req['ncSiteUrl'],
- });
- }
- dbRows.push(dbRow);
- }
- }
- return { offset, dbRows, elapsed };
-}
-
export async function serializeCellValue({
value,
column,
@@ -254,3 +211,67 @@ export async function getColumnByIdOrName(
return column;
}
+
+export async function getDbRows(param: {
+ baseModel: BaseModelSqlv2;
+ view: View;
+ query: any;
+ siteUrl: string;
+}) {
+ const { baseModel, view, query = {}, siteUrl } = param;
+ let offset = +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;
+
+ const listArgs: any = { ...query };
+ try {
+ listArgs.filterArr = JSON.parse(listArgs.filterArrJson);
+ } catch (e) {}
+ try {
+ listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
+ } catch (e) {}
+
+ for (
+ elapsed = 0;
+ elapsed < timeout;
+ offset += limit,
+ temp = process.hrtime(startTime),
+ elapsed = temp[0] * 1000 + temp[1] / 1000000
+ ) {
+ const rows = await nocoExecute(
+ await getAst({
+ query: query,
+ includePkByDefault: false,
+ model: view.model,
+ view,
+ }),
+ await baseModel.list({ ...listArgs, offset, limit }),
+ {},
+ query
+ );
+
+ if (!rows?.length) {
+ offset = -1;
+ break;
+ }
+
+ for (const row of rows) {
+ const dbRow = { ...row };
+
+ for (const column of view.model.columns) {
+ if (isSystemColumn(column) && !view.show_system_fields) continue;
+ dbRow[column.title] = await serializeCellValue({
+ value: row[column.title],
+ column,
+ siteUrl,
+ });
+ }
+ dbRows.push(dbRow);
+ }
+ }
+ return { offset, dbRows, elapsed };
+}
diff --git a/packages/nocodb/src/lib/services/dataService/index.ts b/packages/nocodb/src/lib/services/dataService/index.ts
new file mode 100644
index 0000000000..c125530352
--- /dev/null
+++ b/packages/nocodb/src/lib/services/dataService/index.ts
@@ -0,0 +1,798 @@
+import { nocoExecute } from 'nc-help';
+import getAst from '../../db/sql-data-mapper/lib/sql/helpers/getAst';
+import { NcError } from '../../meta/helpers/catchError';
+import { PagedResponseImpl } from '../../meta/helpers/PagedResponse';
+import { Base, Model, View } from '../../models';
+import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
+import { getViewAndModelByAliasOrId, PathParams } from './helpers';
+
+export async function dataList(param: PathParams & { query: any }) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+ const responseData = await getDataList({ model, view, query: param.query });
+ return responseData;
+}
+
+export async function dataFindOne(param: PathParams & { query: any }) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+ return await getFindOne({ model, view, query: param.query });
+}
+
+export async function dataGroupBy(param: PathParams & { query: any }) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+ return await getDataGroupBy({ model, view, query: param.query });
+}
+
+export async function dataCount(param: PathParams & { query: any }) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ const countArgs: any = { ...param.query };
+ try {
+ countArgs.filterArr = JSON.parse(countArgs.filterArrJson);
+ } catch (e) {}
+
+ const count: number = await baseModel.count(countArgs);
+
+ return { count };
+}
+
+export async function dataInsert(
+ param: PathParams & { body: unknown; cookie: any }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ return await baseModel.insert(param.body, null, param.cookie);
+}
+
+export async function dataUpdate(
+ param: PathParams & { body: unknown; cookie: any; rowId: string }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ return await baseModel.updateByPk(
+ param.rowId,
+ param.body,
+ null,
+ param.cookie
+ );
+}
+
+export async function dataDelete(
+ param: PathParams & { rowId: string; cookie: any }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+ const base = await Base.get(model.base_id);
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ // todo: Should have error http status code
+ const message = await baseModel.hasLTARData(param.rowId, model);
+ if (message.length) {
+ return { message };
+ }
+ return await baseModel.delByPk(param.rowId, null, param.cookie);
+}
+
+export async function getDataList(param: {
+ model: Model;
+ view: View;
+ query: any;
+}) {
+ const { model, view, query = {} } = param;
+
+ 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({ model, query, view });
+
+ const listArgs: any = { ...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(
+ requestObj,
+ await baseModel.list(listArgs),
+ {},
+ listArgs
+ );
+ count = await baseModel.count(listArgs);
+ } catch (e) {
+ console.log(e);
+ NcError.internalServerError(
+ 'Internal Server Error, check server log for more details'
+ );
+ }
+
+ return new PagedResponseImpl(data, {
+ ...query,
+ count,
+ });
+}
+
+export async function getFindOne(param: {
+ model: Model;
+ view: View;
+ query: any;
+}) {
+ const { model, view, query = {} } = param;
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ const args: any = { ...query };
+ try {
+ args.filterArr = JSON.parse(args.filterArrJson);
+ } catch (e) {}
+ try {
+ args.sortArr = JSON.parse(args.sortArrJson);
+ } catch (e) {}
+
+ const data = await baseModel.findOne(args);
+ return data
+ ? await nocoExecute(
+ await getAst({ model, query: args, view }),
+ data,
+ {},
+ {}
+ )
+ : {};
+}
+
+export async function getDataGroupBy(param: {
+ model: Model;
+ view: View;
+ query?: any;
+}) {
+ const { model, view, query = {} } = param;
+
+ 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 = { ...query };
+ const data = await baseModel.groupBy({ ...query });
+ const count = await baseModel.count(listArgs);
+
+ return new PagedResponseImpl(data, {
+ ...query,
+ count,
+ });
+}
+
+export async function dataRead(
+ param: PathParams & { query: any; rowId: string }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ const row = await baseModel.readByPk(param.rowId);
+
+ if (!row) {
+ NcError.notFound('Row not found');
+ }
+
+ return await nocoExecute(
+ await getAst({ model, query: param.query, view }),
+ row,
+ {},
+ param.query
+ );
+}
+
+export async function dataExist(
+ param: PathParams & { rowId: string; query: any }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ return await baseModel.exist(param.rowId);
+}
+
+// todo: Handle the error case where view doesnt belong to model
+export async function groupedDataList(
+ param: PathParams & { query: any; columnId: string }
+) {
+ const { model, view } = await getViewAndModelByAliasOrId(param);
+ const groupedData = await getGroupedDataList({
+ model,
+ view,
+ query: param.query,
+ columnId: param.columnId,
+ });
+ return groupedData;
+}
+
+export async function getGroupedDataList(param: {
+ model;
+ view: View;
+ query: any;
+ columnId: string;
+}) {
+ const { model, view, query = {} } = param;
+
+ 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({ model, 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 = [];
+
+ const groupedData = await baseModel.groupedList({
+ ...listArgs,
+ groupColumnId: param.columnId,
+ });
+ data = await nocoExecute(
+ { key: 1, value: requestObj },
+ groupedData,
+ {},
+ listArgs
+ );
+ const countArr = await baseModel.groupedListCount({
+ ...listArgs,
+ groupColumnId: param.columnId,
+ });
+ 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;
+ });
+
+ return data;
+}
+
+export async function dataListByViewId(param: { viewId: string; query: any }) {
+ const view = await View.get(param.viewId);
+
+ const model = await Model.getByIdOrName({
+ id: view?.fk_model_id || param.viewId,
+ });
+
+ if (!model) NcError.notFound('Table not found');
+
+ return await getDataList({ model, view, query: param.query });
+}
+
+export async function mmList(param: {
+ viewId: string;
+ colId: string;
+ query: any;
+ rowId: string;
+}) {
+ const view = await View.get(param.viewId);
+
+ const model = await Model.getByIdOrName({
+ id: view?.fk_model_id || param.viewId,
+ });
+
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ const key = `${model.title}List`;
+ const requestObj: any = {
+ [key]: 1,
+ };
+
+ const data = (
+ await nocoExecute(
+ requestObj,
+ {
+ [key]: async (args) => {
+ return await baseModel.mmList(
+ {
+ colId: param.colId,
+ parentId: param.rowId,
+ },
+ args
+ );
+ },
+ },
+ {},
+
+ { nested: { [key]: param.query } }
+ )
+ )?.[key];
+
+ const count: any = await baseModel.mmListCount({
+ colId: param.colId,
+ parentId: param.rowId,
+ });
+
+ return new PagedResponseImpl(data, {
+ count,
+ ...param.query,
+ });
+}
+
+export async function mmExcludedList(param: {
+ viewId: string;
+ colId: string;
+ query: any;
+ rowId: string;
+}) {
+ const view = await View.get(param.viewId);
+
+ const model = await Model.getByIdOrName({
+ id: view?.fk_model_id || param.viewId,
+ });
+
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: 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.getMmChildrenExcludedList(
+ {
+ colId: param.colId,
+ pid: param.rowId,
+ },
+ args
+ );
+ },
+ },
+ {},
+
+ { nested: { [key]: param.query } }
+ )
+ )?.[key];
+
+ const count = await baseModel.getMmChildrenExcludedListCount(
+ {
+ colId: param.colId,
+ pid: param.rowId,
+ },
+ param.query
+ );
+
+ return new PagedResponseImpl(data, {
+ count,
+ ...param.query,
+ });
+}
+
+export async function hmExcludedList(param: {
+ viewId: string;
+ colId: string;
+ query: any;
+ rowId: string;
+}) {
+ const view = await View.get(param.viewId);
+
+ const model = await Model.getByIdOrName({
+ id: view?.fk_model_id || param.viewId,
+ });
+
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: 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.getHmChildrenExcludedList(
+ {
+ colId: param.colId,
+ pid: param.rowId,
+ },
+ args
+ );
+ },
+ },
+ {},
+
+ { nested: { [key]: param.query } }
+ )
+ )?.[key];
+
+ const count = await baseModel.getHmChildrenExcludedListCount(
+ {
+ colId: param.colId,
+ pid: param.rowId,
+ },
+ param.query
+ );
+
+ return new PagedResponseImpl(data, {
+ count,
+ ...param.query,
+ });
+}
+
+export async function btExcludedList(param: {
+ viewId: string;
+ colId: string;
+ query: any;
+ rowId: string;
+}) {
+ const view = await View.get(param.viewId);
+
+ const model = await Model.getByIdOrName({
+ id: view?.fk_model_id || param.viewId,
+ });
+
+ if (!model) return NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: 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.getBtChildrenExcludedList(
+ {
+ colId: param.colId,
+ cid: param.rowId,
+ },
+ args
+ );
+ },
+ },
+ {},
+
+ { nested: { [key]: param.query } }
+ )
+ )?.[key];
+
+ const count = await baseModel.getBtChildrenExcludedListCount(
+ {
+ colId: param.colId,
+ cid: param.rowId,
+ },
+ param.query
+ );
+
+ return new PagedResponseImpl(data, {
+ count,
+ ...param.query,
+ });
+}
+
+export async function hmList(param: {
+ viewId: string;
+ colId: string;
+ query: any;
+ rowId: string;
+}) {
+ const view = await View.get(param.viewId);
+
+ const model = await Model.getByIdOrName({
+ id: view?.fk_model_id || param.viewId,
+ });
+
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ const key = `${model.title}List`;
+ const requestObj: any = {
+ [key]: 1,
+ };
+
+ const data = (
+ await nocoExecute(
+ requestObj,
+ {
+ [key]: async (args) => {
+ return await baseModel.hmList(
+ {
+ colId: param.colId,
+ id: param.rowId,
+ },
+ args
+ );
+ },
+ },
+ {},
+ { nested: { [key]: param.query } }
+ )
+ )?.[key];
+
+ const count = await baseModel.hmListCount({
+ colId: param.colId,
+ id: param.rowId,
+ });
+
+ return new PagedResponseImpl(data, {
+ totalRows: count,
+ } as any);
+}
+
+export async function dataReadByViewId(param: {
+ viewId: string;
+ rowId: string;
+ query: any;
+}) {
+ try {
+ const model = await Model.getByIdOrName({
+ id: param.viewId,
+ });
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ return await nocoExecute(
+ await getAst({ model, query: param.query }),
+ await baseModel.readByPk(param.rowId),
+ {},
+ {}
+ );
+ } catch (e) {
+ console.log(e);
+ NcError.internalServerError(
+ 'Internal Server Error, check server log for more details'
+ );
+ }
+}
+
+export async function dataInsertByViewId(param: {
+ viewId: string;
+ body: any;
+ cookie: any;
+}) {
+ const model = await Model.getByIdOrName({
+ id: param.viewId,
+ });
+ if (!model) return NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ return await baseModel.insert(param.body, null, param.cookie);
+}
+
+export async function dataUpdateByViewId(param: {
+ viewId: string;
+ rowId: string;
+ body: any;
+ cookie: any;
+}) {
+ const model = await Model.getByIdOrName({
+ id: param.viewId,
+ });
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ return await baseModel.updateByPk(
+ param.rowId,
+ param.body,
+ null,
+ param.cookie
+ );
+}
+
+export async function dataDeleteByViewId(param: {
+ viewId: string;
+ rowId: string;
+ cookie: any;
+}) {
+ const model = await Model.getByIdOrName({
+ id: param.viewId,
+ });
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ return await baseModel.delByPk(param.rowId, null, param.cookie);
+}
+
+export async function relationDataDelete(param: {
+ viewId: string;
+ colId: string;
+ childId: string;
+ rowId: string;
+ cookie: any;
+}) {
+ const view = await View.get(param.viewId);
+
+ const model = await Model.getByIdOrName({
+ id: view?.fk_model_id || param.viewId,
+ });
+
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ await baseModel.removeChild({
+ colId: param.colId,
+ childId: param.childId,
+ rowId: param.rowId,
+ cookie: param.cookie,
+ });
+
+ return true;
+}
+
+export async function relationDataAdd(param: {
+ viewId: string;
+ colId: string;
+ childId: string;
+ rowId: string;
+ cookie: any;
+}) {
+ const view = await View.get(param.viewId);
+
+ const model = await Model.getByIdOrName({
+ id: view?.fk_model_id || param.viewId,
+ });
+
+ if (!model) NcError.notFound('Table not found');
+
+ const base = await Base.get(model.base_id);
+
+ const baseModel = await Model.getBaseModelSQL({
+ id: model.id,
+ viewId: view?.id,
+ dbDriver: NcConnectionMgrv2.get(base),
+ });
+
+ await baseModel.addChild({
+ colId: param.colId,
+ childId: param.childId,
+ rowId: param.rowId,
+ cookie: param.cookie,
+ });
+
+ return true;
+}
+
+export * from './helpers';
diff --git a/packages/nocodb/src/lib/services/ee/orgTokenService.ts b/packages/nocodb/src/lib/services/ee/orgTokenService.ts
new file mode 100644
index 0000000000..9dd491144c
--- /dev/null
+++ b/packages/nocodb/src/lib/services/ee/orgTokenService.ts
@@ -0,0 +1,20 @@
+import { OrgUserRoles, UserType } from 'nocodb-sdk';
+import { PagedResponseImpl } from '../../meta/helpers/PagedResponse';
+import { ApiToken } from '../../models';
+
+export async function apiTokenListEE(param: { user: UserType; query: any }) {
+ let fk_user_id = param.user.id;
+
+ // if super admin get all tokens
+ if (param.user.roles.includes(OrgUserRoles.SUPER_ADMIN)) {
+ fk_user_id = undefined;
+ }
+
+ return new PagedResponseImpl(
+ await ApiToken.listWithCreatedBy({ ...param.query, fk_user_id }),
+ {
+ ...(param.query || {}),
+ count: await ApiToken.count({}),
+ }
+ );
+}
diff --git a/packages/nocodb/src/lib/services/filterService.ts b/packages/nocodb/src/lib/services/filterService.ts
new file mode 100644
index 0000000000..351d312378
--- /dev/null
+++ b/packages/nocodb/src/lib/services/filterService.ts
@@ -0,0 +1,74 @@
+import { FilterReqType } from 'nocodb-sdk';
+import { validatePayload } from '../meta/api/helpers';
+import Filter from '../models/Filter';
+import { T } from 'nc-help';
+
+export async function hookFilterCreate(param: {
+ filter: FilterReqType;
+ hookId: any;
+}) {
+ validatePayload('swagger.json#/components/schemas/FilterReq', param.filter);
+
+ const filter = await Filter.insert({
+ ...param.filter,
+ fk_hook_id: param.hookId,
+ });
+
+ T.emit('evt', { evt_type: 'hookFilter:created' });
+ return filter;
+}
+
+export async function hookFilterList(param: { hookId: any }) {
+ return Filter.rootFilterListByHook({ hookId: param.hookId });
+}
+
+export async function filterDelete(param: { filterId: string }) {
+ await Filter.delete(param.filterId);
+ T.emit('evt', { evt_type: 'filter:deleted' });
+ return true;
+}
+
+export async function filterCreate(param: {
+ filter: FilterReqType;
+ viewId: string;
+}) {
+ validatePayload('swagger.json#/components/schemas/FilterReq', param.filter);
+
+ const filter = await Filter.insert({
+ ...param.filter,
+ fk_view_id: param.viewId,
+ });
+
+ T.emit('evt', { evt_type: 'filter:created' });
+
+ return filter;
+}
+export async function filterUpdate(param: {
+ filter: FilterReqType;
+ filterId: string;
+}) {
+ validatePayload('swagger.json#/components/schemas/FilterReq', param.filter);
+
+ // todo: type correction
+ const filter = await Filter.update(param.filterId, param.filter as Filter);
+
+ T.emit('evt', { evt_type: 'filter:updated' });
+
+ return filter;
+}
+
+export async function filterChildrenList(param: { filterId: any }) {
+ return Filter.parentFilterList({
+ parentId: param.filterId,
+ });
+}
+
+export async function filterGet(param: { filterId: string }) {
+ const filter = await Filter.get(param.filterId);
+ return filter;
+}
+
+export async function filterList(param: { viewId: string }) {
+ const filter = await Filter.rootFilterList({ viewId: param.viewId });
+ return filter;
+}
diff --git a/packages/nocodb/src/lib/services/formViewColumnService.ts b/packages/nocodb/src/lib/services/formViewColumnService.ts
new file mode 100644
index 0000000000..ecceec1f81
--- /dev/null
+++ b/packages/nocodb/src/lib/services/formViewColumnService.ts
@@ -0,0 +1,19 @@
+import { validatePayload } from '../meta/api/helpers';
+import { FormViewColumn } from '../models';
+import { T } from 'nc-help';
+export async function columnUpdate(param: {
+ formViewColumnId: string;
+ // todo: replace with FormColumnReq
+ formViewColumn: FormViewColumn;
+}) {
+ validatePayload(
+ 'swagger.json#/components/schemas/FormColumnReq',
+ param.formViewColumn
+ );
+
+ T.emit('evt', { evt_type: 'formViewColumn:updated' });
+ return await FormViewColumn.update(
+ param.formViewColumnId,
+ param.formViewColumn
+ );
+}
diff --git a/packages/nocodb/src/lib/services/formViewService.ts b/packages/nocodb/src/lib/services/formViewService.ts
new file mode 100644
index 0000000000..94c6f5c9a5
--- /dev/null
+++ b/packages/nocodb/src/lib/services/formViewService.ts
@@ -0,0 +1,36 @@
+import { T } from 'nc-help';
+import { FormReqType, ViewTypes } from 'nocodb-sdk';
+import { validatePayload } from '../meta/api/helpers';
+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;
+}) {
+ validatePayload('swagger.json#/components/schemas/FormCreateReq', param.body);
+
+ T.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;
+}) {
+ validatePayload('swagger.json#/components/schemas/FormReq', param.body);
+
+ T.emit('evt', { evt_type: 'view:updated', type: 'grid' });
+ await FormView.update(param.formViewId, param.body);
+}
diff --git a/packages/nocodb/src/lib/services/galleryViewService.ts b/packages/nocodb/src/lib/services/galleryViewService.ts
new file mode 100644
index 0000000000..8c564fa74d
--- /dev/null
+++ b/packages/nocodb/src/lib/services/galleryViewService.ts
@@ -0,0 +1,34 @@
+import { GalleryReqType, ViewTypes } from 'nocodb-sdk';
+import { T } from 'nc-help';
+import { validatePayload } from '../meta/api/helpers';
+import { GalleryView, View } from '../models';
+
+export async function galleryViewGet(param: { galleryViewId: string }) {
+ return await GalleryView.get(param.galleryViewId);
+}
+
+export async function galleryViewCreate(param: {
+ tableId: string;
+ gallery: GalleryReqType;
+}) {
+ validatePayload('swagger.json#/components/schemas/GalleryReq', param.gallery);
+
+ T.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;
+}) {
+ validatePayload('swagger.json#/components/schemas/GalleryReq', param.gallery);
+
+ T.emit('evt', { evt_type: 'view:updated', type: 'gallery' });
+ await GalleryView.update(param.galleryViewId, param.gallery);
+}
diff --git a/packages/nocodb/src/lib/services/gridViewColumnService.ts b/packages/nocodb/src/lib/services/gridViewColumnService.ts
new file mode 100644
index 0000000000..70d5d3df76
--- /dev/null
+++ b/packages/nocodb/src/lib/services/gridViewColumnService.ts
@@ -0,0 +1,18 @@
+import { GridColumnReqType } from 'nocodb-sdk';
+import { validatePayload } from '../meta/api/helpers';
+import GridViewColumn from '../models/GridViewColumn';
+import { T } from 'nc-help';
+
+export async function columnList(param: { gridViewId: string }) {
+ return await GridViewColumn.list(param.gridViewId);
+}
+
+export async function gridColumnUpdate(param: {
+ gridViewColumnId: string;
+ grid: GridColumnReqType;
+}) {
+ validatePayload('swagger.json#/components/schemas/GridColumnReq', param.grid);
+
+ T.emit('evt', { evt_type: 'gridViewColumn:updated' });
+ return await GridViewColumn.update(param.gridViewColumnId, param.grid);
+}
diff --git a/packages/nocodb/src/lib/services/gridViewService.ts b/packages/nocodb/src/lib/services/gridViewService.ts
new file mode 100644
index 0000000000..92d01ced57
--- /dev/null
+++ b/packages/nocodb/src/lib/services/gridViewService.ts
@@ -0,0 +1,30 @@
+import { T } from 'nc-help';
+import { GridReqType, ViewTypes } from 'nocodb-sdk';
+import { validatePayload } from '../meta/api/helpers';
+import { View } from '../models';
+import { GridView } from '../models';
+
+export async function gridViewCreate(param: {
+ tableId: string;
+ grid: GridReqType;
+}) {
+ validatePayload('swagger.json#/components/schemas/GridReq', param.grid);
+
+ const view = await View.insert({
+ ...param.grid,
+ // todo: sanitize
+ fk_model_id: param.tableId,
+ type: ViewTypes.GRID,
+ });
+ T.emit('evt', { evt_type: 'vtable:created', show_as: 'grid' });
+ return view;
+}
+
+// todo: json schema validation
+export async function gridViewUpdate(param: {
+ viewId: string;
+ grid: GridReqType;
+}) {
+ T.emit('evt', { evt_type: 'view:updated', type: 'grid' });
+ return await GridView.update(param.viewId, param.grid);
+}
diff --git a/packages/nocodb/src/lib/services/hookFilterService.ts b/packages/nocodb/src/lib/services/hookFilterService.ts
new file mode 100644
index 0000000000..b8b1e04a42
--- /dev/null
+++ b/packages/nocodb/src/lib/services/hookFilterService.ts
@@ -0,0 +1,66 @@
+import { T } from 'nc-help';
+import { FilterReqType } from 'nocodb-sdk';
+import { validatePayload } from '../meta/api/helpers';
+import Filter from '../models/Filter';
+
+export async function filterGet(param: { hookId: string }) {
+ const filter = await Filter.getFilterObject({ hookId: param.hookId });
+
+ return filter;
+}
+
+export async function filterList(param: { hookId: string }) {
+ const filters = await Filter.rootFilterListByHook({
+ hookId: param.hookId,
+ });
+
+ return filters;
+}
+
+export async function filterChildrenRead(param: {
+ hookId: string;
+ filterParentId: string;
+}) {
+ const filter = await Filter.parentFilterListByHook({
+ hookId: param.hookId,
+ parentId: param.filterParentId,
+ });
+
+ return filter;
+}
+
+export async function filterCreate(param: {
+ hookId: string;
+ filter: FilterReqType;
+}) {
+ validatePayload('swagger.json#/components/schemas/FilterReq', param.filter);
+
+ const filter = await Filter.insert({
+ ...param.filter,
+ fk_hook_id: param.hookId,
+ });
+
+ T.emit('evt', { evt_type: 'hookFilter:created' });
+ return filter;
+}
+
+export async function filterUpdate(param: {
+ hookId: string;
+ filterId: string;
+ filter: FilterReqType;
+}) {
+ validatePayload('swagger.json#/components/schemas/FilterReq', param.filter);
+
+ const filter = await Filter.update(param.filterId, {
+ ...param.filter,
+ fk_hook_id: param.hookId,
+ } as Filter);
+ T.emit('evt', { evt_type: 'hookFilter:updated' });
+ return filter;
+}
+
+export async function filterDelete(param: { filterId: string }) {
+ await Filter.delete(param.filterId);
+ T.emit('evt', { evt_type: 'hookFilter:deleted' });
+ return true;
+}
diff --git a/packages/nocodb/src/lib/services/hookService.ts b/packages/nocodb/src/lib/services/hookService.ts
new file mode 100644
index 0000000000..9acf6d4b48
--- /dev/null
+++ b/packages/nocodb/src/lib/services/hookService.ts
@@ -0,0 +1,79 @@
+import { T } from 'nc-help';
+import { validatePayload } from '../meta/api/helpers';
+import { Hook, Model } from '../models';
+import { HookReqType, HookTestReqType } from 'nocodb-sdk';
+
+import { invokeWebhook } from '../meta/helpers/webhookHelpers';
+import populateSamplePayload from '../meta/helpers/populateSamplePayload';
+
+export async function hookList(param: { tableId: string }) {
+ // todo: pagination
+ return await Hook.list({ fk_model_id: param.tableId });
+}
+
+export async function hookCreate(param: {
+ tableId: string;
+ hook: HookReqType;
+}) {
+ validatePayload('swagger.json#/components/schemas/HookReq', param.hook);
+
+ T.emit('evt', { evt_type: 'webhooks:created' });
+ // todo: type correction
+ const hook = await Hook.insert({
+ ...param.hook,
+ fk_model_id: param.tableId,
+ } as any);
+ return hook;
+}
+
+export async function hookDelete(param: { hookId: string }) {
+ T.emit('evt', { evt_type: 'webhooks:deleted' });
+ await Hook.delete(param.hookId);
+ return true;
+}
+
+export async function hookUpdate(param: { hookId: string; hook: HookReqType }) {
+ validatePayload('swagger.json#/components/schemas/HookReq', param.hook);
+
+ T.emit('evt', { evt_type: 'webhooks:updated' });
+
+ // todo: correction in swagger
+ return await Hook.update(param.hookId, param.hook as any);
+}
+
+export async function hookTest(param: {
+ tableId: string;
+ hookTest: HookTestReqType;
+}) {
+ validatePayload(
+ 'swagger.json#/components/schemas/HookTestReq',
+ param.hookTest
+ );
+
+ const model = await Model.getByIdOrName({ id: param.tableId });
+
+ const {
+ hook,
+ payload: { data, user },
+ } = param.hookTest;
+ await invokeWebhook(
+ new Hook(hook),
+ model,
+ data,
+ user,
+ (hook as any)?.filters,
+ true
+ );
+
+ T.emit('evt', { evt_type: 'webhooks:tested' });
+
+ return true;
+}
+export async function tableSampleData(param: {
+ tableId: string;
+ operation: 'insert' | 'update';
+}) {
+ const model = await Model.getByIdOrName({ id: param.tableId });
+
+ return await populateSamplePayload(model, false, param.operation);
+}
diff --git a/packages/nocodb/src/lib/services/index.ts b/packages/nocodb/src/lib/services/index.ts
new file mode 100644
index 0000000000..c58049aa62
--- /dev/null
+++ b/packages/nocodb/src/lib/services/index.ts
@@ -0,0 +1,38 @@
+export * as projectService from './projectService';
+export * as tableService from './tableService';
+export * as columnService from './columnService';
+export * as filterService from './filterService';
+export * as sortService from './sortService';
+export * as baseService from './baseService';
+export * as apiTokenService from './apiTokenService';
+export * as viewService from './viewService';
+export * as hookService from './hookService';
+export * as pluginService from './pluginService';
+export * as utilService from './utilService';
+export * as formViewService from './formViewService';
+export * as formViewColumnService from './formViewColumnService';
+export * as gridViewService from './gridViewService';
+export * as galleryViewService from './galleryViewService';
+export * as kanbanViewService from './kanbanViewService';
+export * as gridViewColumnService from './gridViewColumnService';
+export * as viewColumnService from './viewColumnService';
+export * as metaDiffService from './metaDiffService';
+export * as mapViewService from './mapViewService';
+export * as modelVisibilityService from './modelVisibilityService';
+export * as sharedBaseService from './sharedBaseService';
+export * as orgUserService from './orgUserService';
+export * as orgLicenseService from './orgLicenseService';
+export * as projectUserService from './projectUserService';
+export * as attachmentService from './attachmentService';
+export * as hookFilterService from './hookFilterService';
+export * as dataService from './dataService';
+export * as bulkDataService from './dataService/bulkData';
+export * as dataAliasNestedService from './dataService/dataAliasNestedService';
+export * as cacheService from './cacheService';
+export * as auditService from './auditService';
+export * as swaggerService from './swaggerService';
+export * as userService from './userService';
+export * as syncService from './syncService';
+export * from './public';
+export * as orgTokenService from './orgTokenService';
+export * as orgTokenServiceEE from './ee/orgTokenService';
diff --git a/packages/nocodb/src/lib/services/kanbanViewService.ts b/packages/nocodb/src/lib/services/kanbanViewService.ts
new file mode 100644
index 0000000000..e6561ae323
--- /dev/null
+++ b/packages/nocodb/src/lib/services/kanbanViewService.ts
@@ -0,0 +1,35 @@
+import { KanbanReqType, ViewTypes } from 'nocodb-sdk';
+import { validatePayload } from '../meta/api/helpers';
+import { KanbanView, View } from '../models';
+import { T } 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;
+}) {
+ validatePayload('swagger.json#/components/schemas/KanbanReq', param.kanban),
+ T.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;
+}) {
+ validatePayload(
+ 'swagger.json#/components/schemas/KanbanUpdateReq',
+ param.kanban
+ ),
+ T.emit('evt', { evt_type: 'view:updated', type: 'kanban' });
+ return await KanbanView.update(param.kanbanViewId, param.kanban);
+}
diff --git a/packages/nocodb/src/lib/services/mapViewService.ts b/packages/nocodb/src/lib/services/mapViewService.ts
new file mode 100644
index 0000000000..199baa9d13
--- /dev/null
+++ b/packages/nocodb/src/lib/services/mapViewService.ts
@@ -0,0 +1,33 @@
+import { MapType, ViewTypes } from 'nocodb-sdk';
+import View from '../models/View';
+import { T } from 'nc-help';
+import MapView from '../models/MapView';
+
+export async function mapViewGet(param: { mapViewId: string }) {
+ return await MapView.get(param.mapViewId);
+}
+
+export async function mapViewCreate(param: {
+ tableId: string;
+ // todo: add MapReq in schema
+ map: MapType;
+}) {
+ T.emit('evt', { evt_type: 'vtable:created', show_as: 'map' });
+ const view = await View.insert({
+ ...param.map,
+ // todo: sanitize
+ fk_model_id: param.tableId,
+ type: ViewTypes.MAP,
+ });
+ return view;
+}
+
+export async function mapViewUpdate(param: {
+ mapViewId: string;
+ // todo: add MapReq in schema
+ map: MapType;
+}) {
+ T.emit('evt', { evt_type: 'view:updated', type: 'map' });
+ // todo: type correction
+ return await MapView.update(param.mapViewId, param.map as any);
+}
diff --git a/packages/nocodb/src/lib/meta/api/metaDiffApis.ts b/packages/nocodb/src/lib/services/metaDiffService.ts
similarity index 93%
rename from packages/nocodb/src/lib/meta/api/metaDiffApis.ts
rename to packages/nocodb/src/lib/services/metaDiffService.ts
index 35f8118e21..274ba8e705 100644
--- a/packages/nocodb/src/lib/meta/api/metaDiffApis.ts
+++ b/packages/nocodb/src/lib/services/metaDiffService.ts
@@ -1,22 +1,23 @@
// // Project CRUD
-import { Tele } from 'nc-help';
-import ncMetaAclMw from '../helpers/ncMetaAclMw';
-import Model from '../../models/Model';
-import Project from '../../models/Project';
-import NcConnectionMgrv2 from '../../utils/common/NcConnectionMgrv2';
+import { T } from 'nc-help';
+import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2';
import { isVirtualCol, ModelTypes, RelationTypes, UITypes } from 'nocodb-sdk';
-import { Router } from 'express';
-import Base from '../../models/Base';
-import ModelXcMetaFactory from '../../db/sql-mgr/code/models/xc/ModelXcMetaFactory';
-import Column from '../../models/Column';
-import LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn';
-import { getUniqueColumnAliasName } from '../helpers/getUniqueName';
-import NcHelp from '../../utils/NcHelp';
-import getTableNameAlias, { getColumnNameAlias } from '../helpers/getTableName';
-import mapDefaultDisplayValue from '../helpers/mapDefaultDisplayValue';
-import getColumnUiType from '../helpers/getColumnUiType';
-import { metaApiMetrics } from '../helpers/apiMetrics';
+import {
+ Base,
+ Column,
+ LinkToAnotherRecordColumn,
+ Model,
+ Project,
+} from '../models';
+import ModelXcMetaFactory from '../db/sql-mgr/code/models/xc/ModelXcMetaFactory';
+import { getUniqueColumnAliasName } from '../meta/helpers/getUniqueName';
+import NcHelp from '../utils/NcHelp';
+import getTableNameAlias, {
+ getColumnNameAlias,
+} from '../meta/helpers/getTableName';
+import mapDefaultDisplayValue from '../meta/helpers/mapDefaultDisplayValue';
+import getColumnUiType from '../meta/helpers/getColumnUiType';
export enum MetaDiffType {
TABLE_NEW = 'TABLE_NEW',
@@ -545,8 +546,8 @@ async function getMetaDiff(
return changes;
}
-export async function metaDiff(req, res) {
- const project = await Project.getWithInfo(req.params.projectId);
+export async function metaDiff(param: { projectId: string }) {
+ const project = await Project.getWithInfo(param.projectId);
let changes = [];
for (const base of project.bases) {
try {
@@ -558,22 +559,25 @@ export async function metaDiff(req, res) {
}
}
- res.json(changes);
+ return changes;
}
-export async function baseMetaDiff(req, res) {
- const project = await Project.getWithInfo(req.params.projectId);
- const base = await Base.get(req.params.baseId);
+export async function baseMetaDiff(param: {
+ projectId: string;
+ baseId: string;
+}) {
+ const project = await Project.getWithInfo(param.projectId);
+ const base = await Base.get(param.baseId);
let changes = [];
const sqlClient = await NcConnectionMgrv2.getSqlClient(base);
changes = await getMetaDiff(sqlClient, project, base);
- res.json(changes);
+ return changes;
}
-export async function metaDiffSync(req, res) {
- const project = await Project.getWithInfo(req.params.projectId);
+export async function metaDiffSync(param: { projectId: string }) {
+ const project = await Project.getWithInfo(param.projectId);
for (const base of project.bases) {
const virtualColumnInsert: Array<() => Promise> = [];
@@ -771,14 +775,17 @@ export async function metaDiffSync(req, res) {
await extractAndGenerateManyToManyRelations(await base.getModels());
}
- Tele.emit('evt', { evt_type: 'metaDiff:synced' });
+ T.emit('evt', { evt_type: 'metaDiff:synced' });
- res.json({ msg: 'success' });
+ return true;
}
-export async function baseMetaDiffSync(req, res) {
- const project = await Project.getWithInfo(req.params.projectId);
- const base = await Base.get(req.params.baseId);
+export async function baseMetaDiffSync(param: {
+ projectId: string;
+ baseId: string;
+}) {
+ const project = await Project.getWithInfo(param.projectId);
+ const base = await Base.get(param.baseId);
const virtualColumnInsert: Array<() => Promise> = [];
@@ -960,9 +967,9 @@ export async function baseMetaDiffSync(req, res) {
// populate m2m relations
await extractAndGenerateManyToManyRelations(await base.getModels());
- Tele.emit('evt', { evt_type: 'baseMetaDiff:synced' });
+ T.emit('evt', { evt_type: 'baseMetaDiff:synced' });
- res.json({ msg: 'success' });
+ return true;
}
async function isMMRelationExist(
@@ -1094,26 +1101,3 @@ export async function extractAndGenerateManyToManyRelations(
}
}
}
-
-const router = Router();
-router.get(
- '/api/v1/db/meta/projects/:projectId/meta-diff',
- metaApiMetrics,
- ncMetaAclMw(metaDiff, 'metaDiff')
-);
-router.post(
- '/api/v1/db/meta/projects/:projectId/meta-diff',
- metaApiMetrics,
- ncMetaAclMw(metaDiffSync, 'metaDiffSync')
-);
-router.get(
- '/api/v1/db/meta/projects/:projectId/meta-diff/:baseId',
- metaApiMetrics,
- ncMetaAclMw(baseMetaDiff, 'baseMetaDiff')
-);
-router.post(
- '/api/v1/db/meta/projects/:projectId/meta-diff/:baseId',
- metaApiMetrics,
- ncMetaAclMw(baseMetaDiffSync, 'baseMetaDiffSync')
-);
-export default router;
diff --git a/packages/nocodb/src/lib/services/modelVisibilityService.ts b/packages/nocodb/src/lib/services/modelVisibilityService.ts
new file mode 100644
index 0000000000..719d3de487
--- /dev/null
+++ b/packages/nocodb/src/lib/services/modelVisibilityService.ts
@@ -0,0 +1,102 @@
+import { VisibilityRuleReqType } from 'nocodb-sdk';
+import { validatePayload } from '../meta/api/helpers';
+import { NcError } from '../meta/helpers/catchError';
+import ModelRoleVisibility from '../models/ModelRoleVisibility';
+import { T } from 'nc-help';
+import { Model, View } from '../models';
+
+export async function xcVisibilityMetaSetAll(param: {
+ visibilityRule: VisibilityRuleReqType;
+ projectId: string;
+}) {
+ validatePayload(
+ 'swagger.json#/components/schemas/VisibilityRuleReq',
+ param.visibilityRule
+ ),
+ T.emit('evt', { evt_type: 'uiAcl:updated' });
+ for (const d of param.visibilityRule) {
+ for (const role of Object.keys(d.disabled)) {
+ const view = await View.get(d.id);
+
+ if (view.project_id !== param.projectId) {
+ NcError.badRequest('View does not belong to the project');
+ }
+
+ const dataInDb = await ModelRoleVisibility.get({
+ role,
+ fk_view_id: d.id,
+ });
+ if (dataInDb) {
+ if (d.disabled[role]) {
+ if (!dataInDb.disabled) {
+ await ModelRoleVisibility.update(d.id, role, {
+ disabled: d.disabled[role],
+ });
+ }
+ } else {
+ await dataInDb.delete();
+ }
+ } else if (d.disabled[role]) {
+ await ModelRoleVisibility.insert({
+ fk_view_id: d.id,
+ disabled: d.disabled[role],
+ role,
+ });
+ }
+ }
+ }
+ T.emit('evt', { evt_type: 'uiAcl:updated' });
+
+ return true;
+}
+
+export async function xcVisibilityMetaGet(param: {
+ projectId: string;
+ includeM2M?: boolean;
+ models?: Model[];
+}) {
+ const { includeM2M = true, projectId, models: _models } = param ?? {};
+
+ // todo: move to
+ const roles = ['owner', 'creator', 'viewer', 'editor', 'commenter', 'guest'];
+
+ const defaultDisabled = roles.reduce((o, r) => ({ ...o, [r]: false }), {});
+
+ let models =
+ _models ||
+ (await Model.list({
+ project_id: projectId,
+ base_id: undefined,
+ }));
+
+ models = includeM2M ? models : (models.filter((t) => !t.mm) as Model[]);
+
+ const result = await models.reduce(async (_obj, model) => {
+ const obj = await _obj;
+
+ const views = await model.getViews();
+ for (const view of views) {
+ obj[view.id] = {
+ ptn: model.table_name,
+ _ptn: model.title,
+ ptype: model.type,
+ tn: view.title,
+ _tn: view.title,
+ table_meta: model.meta,
+ ...view,
+ disabled: { ...defaultDisabled },
+ };
+ }
+
+ return obj;
+ }, Promise.resolve({}));
+
+ const disabledList = await ModelRoleVisibility.list(projectId);
+
+ for (const d of disabledList) {
+ if (result[d.fk_view_id])
+ result[d.fk_view_id].disabled[d.role] = !!d.disabled;
+ }
+
+ return Object.values(result);
+}
diff --git a/packages/nocodb/src/lib/services/orgLicenseService.ts b/packages/nocodb/src/lib/services/orgLicenseService.ts
new file mode 100644
index 0000000000..d6f8033575
--- /dev/null
+++ b/packages/nocodb/src/lib/services/orgLicenseService.ts
@@ -0,0 +1,18 @@
+import { NC_LICENSE_KEY } from '../constants';
+import { validatePayload } from '../meta/api/helpers';
+import Store from '../models/Store';
+import Noco from '../Noco';
+
+export async function licenseGet() {
+ const license = await Store.get(NC_LICENSE_KEY);
+
+ return { key: license?.value };
+}
+
+export async function licenseSet(param: { key: string }) {
+ validatePayload('swagger.json#/components/schemas/LicenseReq', param);
+
+ await Store.saveOrUpdate({ value: param.key, key: NC_LICENSE_KEY });
+ await Noco.loadEEState();
+ return true;
+}
diff --git a/packages/nocodb/src/lib/services/orgTokenService.ts b/packages/nocodb/src/lib/services/orgTokenService.ts
new file mode 100644
index 0000000000..fdee8e5fbe
--- /dev/null
+++ b/packages/nocodb/src/lib/services/orgTokenService.ts
@@ -0,0 +1,59 @@
+import { ApiTokenReqType, OrgUserRoles } from 'nocodb-sdk';
+import { validatePayload } from '../meta/api/helpers';
+import { User } from '../models';
+import ApiToken from '../models/ApiToken';
+import { T } from 'nc-help';
+import { NcError } from '../meta/helpers/catchError';
+import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
+
+export async function apiTokenList(param: { user: User; query: any }) {
+ const fk_user_id = param.user.id;
+ let includeUnmappedToken = false;
+ if (param.user.roles.includes(OrgUserRoles.SUPER_ADMIN)) {
+ includeUnmappedToken = true;
+ }
+
+ return new PagedResponseImpl(
+ await ApiToken.listWithCreatedBy({
+ ...param.query,
+ fk_user_id,
+ includeUnmappedToken,
+ }),
+ {
+ ...param.query,
+ count: await ApiToken.count({
+ includeUnmappedToken,
+ fk_user_id,
+ }),
+ }
+ );
+}
+
+export async function apiTokenCreate(param: {
+ user: User;
+ apiToken: ApiTokenReqType;
+}) {
+ validatePayload(
+ 'swagger.json#/components/schemas/ApiTokenReq',
+ param.apiToken
+ );
+
+ T.emit('evt', { evt_type: 'org:apiToken:created' });
+ return await ApiToken.insert({
+ ...param.apiToken,
+ fk_user_id: param['user'].id,
+ });
+}
+
+export async function apiTokenDelete(param: { user: User; token: string }) {
+ const fk_user_id = param.user.id;
+ const apiToken = await ApiToken.getByToken(param.token);
+ if (
+ !param.user.roles.includes(OrgUserRoles.SUPER_ADMIN) &&
+ apiToken.fk_user_id !== fk_user_id
+ ) {
+ NcError.notFound('Token not found');
+ }
+ T.emit('evt', { evt_type: 'org:apiToken:deleted' });
+ return await ApiToken.delete(param.token);
+}
diff --git a/packages/nocodb/src/lib/services/orgUserService.ts b/packages/nocodb/src/lib/services/orgUserService.ts
new file mode 100644
index 0000000000..b4e2d2adac
--- /dev/null
+++ b/packages/nocodb/src/lib/services/orgUserService.ts
@@ -0,0 +1,271 @@
+import {
+ AuditOperationSubTypes,
+ AuditOperationTypes,
+ PluginCategory,
+ UserType,
+} from 'nocodb-sdk';
+import { v4 as uuidv4 } from 'uuid';
+import validator from 'validator';
+import { OrgUserRoles } from 'nocodb-sdk';
+import { NC_APP_SETTINGS } from '../constants';
+import { validatePayload } from '../meta/api/helpers';
+import { Audit, ProjectUser, Store, SyncSource, User } from '../models';
+import Noco from '../Noco';
+import { MetaTable } from '../utils/globals';
+import { T } from 'nc-help';
+import { NcError } from '../meta/helpers/catchError';
+import { extractProps } from '../meta/helpers/extractProps';
+import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
+import { randomTokenString } from '../meta/helpers/stringHelpers';
+import { sendInviteEmail } from './projectUserService';
+
+export async function userList(param: {
+ // todo: add better typing
+ query: Record;
+}) {
+ const { query = {} } = param;
+
+ return new PagedResponseImpl(await User.list(query), {
+ ...query,
+ count: await User.count(query),
+ });
+}
+
+export async function userUpdate(param: {
+ // todo: better typing
+ user: Partial;
+ userId: string;
+}) {
+ validatePayload('swagger.json#/components/schemas/OrgUserReq', param.user);
+
+ const updateBody = extractProps(param.user, ['roles']);
+
+ const user = await User.get(param.userId);
+
+ if (user.roles.includes(OrgUserRoles.SUPER_ADMIN)) {
+ NcError.badRequest('Cannot update super admin roles');
+ }
+
+ return await User.update(param.userId, {
+ ...updateBody,
+ token_version: null,
+ });
+}
+
+export async function userDelete(param: { userId: string }) {
+ const ncMeta = await Noco.ncMeta.startTransaction();
+ try {
+ const user = await User.get(param.userId, ncMeta);
+
+ if (user.roles.includes(OrgUserRoles.SUPER_ADMIN)) {
+ NcError.badRequest('Cannot delete super admin');
+ }
+
+ // delete project user entry and assign to super admin
+ const projectUsers = await ProjectUser.getProjectsIdList(
+ param.userId,
+ ncMeta
+ );
+
+ // todo: clear cache
+
+ // TODO: assign super admin as project owner
+ for (const projectUser of projectUsers) {
+ await ProjectUser.delete(
+ projectUser.project_id,
+ projectUser.fk_user_id,
+ ncMeta
+ );
+ }
+
+ // delete sync source entry
+ await SyncSource.deleteByUserId(param.userId, ncMeta);
+
+ // delete user
+ await User.delete(param.userId, ncMeta);
+ await ncMeta.commit();
+ } catch (e) {
+ await ncMeta.rollback(e);
+ throw e;
+ }
+
+ return true;
+}
+
+export async function userAdd(param: {
+ user: UserType;
+ projectId: string;
+ // todo: refactor
+ req: any;
+}) {
+ validatePayload('swagger.json#/components/schemas/OrgUserReq', param.user);
+
+ // allow only viewer or creator role
+ if (
+ param.user.roles &&
+ ![OrgUserRoles.VIEWER, OrgUserRoles.CREATOR].includes(
+ param.user.roles as OrgUserRoles
+ )
+ ) {
+ NcError.badRequest('Invalid role');
+ }
+
+ // extract emails from request body
+ const emails = (param.user.email || '')
+ .toLowerCase()
+ .split(/\s*,\s*/)
+ .map((v) => v.trim());
+
+ // check for invalid emails
+ const invalidEmails = emails.filter((v) => !validator.isEmail(v));
+
+ if (!emails.length) {
+ return NcError.badRequest('Invalid email address');
+ }
+ if (invalidEmails.length) {
+ NcError.badRequest('Invalid email address : ' + invalidEmails.join(', '));
+ }
+
+ const invite_token = uuidv4();
+ const error = [];
+
+ for (const email of emails) {
+ // add user to project if user already exist
+ const user = await User.getByEmail(email);
+
+ if (user) {
+ NcError.badRequest('User already exist');
+ } else {
+ try {
+ // create new user with invite token
+ await User.insert({
+ invite_token,
+ invite_token_expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
+ email,
+ roles: param.user.roles || OrgUserRoles.VIEWER,
+ token_version: randomTokenString(),
+ });
+
+ const count = await User.count();
+ T.emit('evt', { evt_type: 'org:user:invite', count });
+
+ await Audit.insert({
+ op_type: AuditOperationTypes.ORG_USER,
+ op_sub_type: AuditOperationSubTypes.INVITE,
+ user: param.req.user.email,
+ description: `invited ${email} to ${param.projectId} project `,
+ ip: param.req.clientIp,
+ });
+ // in case of single user check for smtp failure
+ // and send back token if failed
+ if (
+ emails.length === 1 &&
+ !(await sendInviteEmail(email, invite_token, param.req))
+ ) {
+ return { invite_token, email };
+ } else {
+ sendInviteEmail(email, invite_token, param.req);
+ }
+ } catch (e) {
+ console.log(e);
+ if (emails.length === 1) {
+ throw e;
+ } else {
+ error.push({ email, error: e.message });
+ }
+ }
+ }
+ }
+
+ if (emails.length === 1) {
+ return {
+ msg: 'success',
+ };
+ } else {
+ return { invite_token, emails, error };
+ }
+}
+
+export async function userSettings(_param): Promise {
+ NcError.notImplemented();
+}
+
+export async function userInviteResend(param: {
+ userId: string;
+ req: any;
+}): Promise {
+ const user = await User.get(param.userId);
+
+ if (!user) {
+ NcError.badRequest(`User with id '${param.userId}' not found`);
+ }
+
+ const invite_token = uuidv4();
+
+ await User.update(user.id, {
+ invite_token,
+ invite_token_expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
+ });
+
+ const pluginData = await Noco.ncMeta.metaGet2(null, null, MetaTable.PLUGIN, {
+ category: PluginCategory.EMAIL,
+ active: true,
+ });
+
+ if (!pluginData) {
+ NcError.badRequest(
+ `No Email Plugin is found. Please go to App Store to configure first or copy the invitation URL to users instead.`
+ );
+ }
+
+ await sendInviteEmail(user.email, invite_token, param.req);
+
+ await Audit.insert({
+ op_type: AuditOperationTypes.ORG_USER,
+ op_sub_type: AuditOperationSubTypes.RESEND_INVITE,
+ user: user.email,
+ description: `resent a invite to ${user.email} `,
+ ip: param.req.clientIp,
+ });
+
+ return true;
+}
+
+export async function generateResetUrl(param: {
+ userId: string;
+ siteUrl: string;
+}) {
+ const user = await User.get(param.userId);
+
+ if (!user) {
+ NcError.badRequest(`User with id '${param.userId}' not found`);
+ }
+ const token = uuidv4();
+ await User.update(user.id, {
+ email: user.email,
+ reset_password_token: token,
+ reset_password_expires: new Date(Date.now() + 60 * 60 * 1000),
+ token_version: null,
+ });
+
+ return {
+ reset_password_token: token,
+ reset_password_url: param.siteUrl + `/auth/password/reset/${token}`,
+ };
+}
+
+export async function appSettingsGet() {
+ let settings = {};
+ try {
+ settings = JSON.parse((await Store.get(NC_APP_SETTINGS))?.value);
+ } catch {}
+ return settings;
+}
+
+export async function appSettingsSet(param: { settings: any }) {
+ await Store.saveOrUpdate({
+ value: JSON.stringify(param.settings),
+ key: NC_APP_SETTINGS,
+ });
+ return true;
+}
diff --git a/packages/nocodb/src/lib/services/pluginService.ts b/packages/nocodb/src/lib/services/pluginService.ts
new file mode 100644
index 0000000000..67302d8ce7
--- /dev/null
+++ b/packages/nocodb/src/lib/services/pluginService.ts
@@ -0,0 +1,36 @@
+import { T } from 'nc-help';
+import { validatePayload } from '../meta/api/helpers';
+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 }) {
+ validatePayload('swagger.json#/components/schemas/PluginTestReq', param.body);
+
+ T.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;
+}) {
+ validatePayload('swagger.json#/components/schemas/PluginReq', param.plugin);
+
+ const plugin = await Plugin.update(param.pluginId, param.plugin);
+ T.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);
+}
diff --git a/packages/nocodb/src/lib/services/projectService.ts b/packages/nocodb/src/lib/services/projectService.ts
new file mode 100644
index 0000000000..08a848f7a5
--- /dev/null
+++ b/packages/nocodb/src/lib/services/projectService.ts
@@ -0,0 +1,171 @@
+import DOMPurify from 'isomorphic-dompurify';
+import { OrgUserRoles, ProjectReqType } from 'nocodb-sdk';
+import { promisify } from 'util';
+import { populateMeta, validatePayload } from '../meta/api/helpers';
+import { extractPropsAndSanitize } from '../meta/helpers/extractProps';
+import syncMigration from '../meta/helpers/syncMigration';
+import Project from '../models/Project';
+import ProjectUser from '../models/ProjectUser';
+import { customAlphabet } from 'nanoid';
+import Noco from '../Noco';
+import NcConfigFactory from '../utils/NcConfigFactory';
+import { NcError } from '../meta/helpers/catchError';
+
+export async function projectCreate(param: {
+ project: ProjectReqType;
+ user: any;
+}) {
+ validatePayload('swagger.json#/components/schemas/ProjectReq', param.project);
+
+ const projectBody: ProjectReqType & Record = param.project;
+ if (!projectBody.external) {
+ const ranId = nanoid();
+ projectBody.prefix = `nc_${ranId}__`;
+ projectBody.is_meta = true;
+ if (process.env.NC_MINIMAL_DBS) {
+ // if env variable NC_MINIMAL_DBS is set, then create a SQLite file/connection for each project
+ // each file will be named as nc_.db
+ const fs = require('fs');
+ const toolDir = NcConfigFactory.getToolDir();
+ const nanoidv2 = customAlphabet(
+ '1234567890abcdefghijklmnopqrstuvwxyz',
+ 14
+ );
+ if (!(await promisify(fs.exists)(`${toolDir}/nc_minimal_dbs`))) {
+ await promisify(fs.mkdir)(`${toolDir}/nc_minimal_dbs`);
+ }
+ const dbId = nanoidv2();
+ const projectTitle = DOMPurify.sanitize(projectBody.title);
+ projectBody.prefix = '';
+ projectBody.bases = [
+ {
+ type: 'sqlite3',
+ config: {
+ client: 'sqlite3',
+ connection: {
+ client: 'sqlite3',
+ database: projectTitle,
+ connection: {
+ filename: `${toolDir}/nc_minimal_dbs/${projectTitle}_${dbId}.db`,
+ },
+ },
+ },
+ inflection_column: 'camelize',
+ inflection_table: 'camelize',
+ },
+ ];
+ } else {
+ const db = Noco.getConfig().meta?.db;
+ projectBody.bases = [
+ {
+ type: db?.client,
+ config: null,
+ is_meta: true,
+ inflection_column: 'camelize',
+ inflection_table: 'camelize',
+ },
+ ];
+ }
+ } else {
+ if (process.env.NC_CONNECT_TO_EXTERNAL_DB_DISABLED) {
+ NcError.badRequest('Connecting to external db is disabled');
+ }
+ projectBody.is_meta = false;
+ }
+
+ if (projectBody?.title.length > 50) {
+ NcError.badRequest('Project title exceeds 50 characters');
+ }
+
+ if (await Project.getByTitle(projectBody?.title)) {
+ NcError.badRequest('Project title already in use');
+ }
+
+ projectBody.title = DOMPurify.sanitize(projectBody.title);
+ projectBody.slug = projectBody.title;
+
+ const project = await Project.createProject(projectBody);
+ await ProjectUser.insert({
+ fk_user_id: (param as any).user.id,
+ project_id: project.id,
+ roles: 'owner',
+ });
+
+ await syncMigration(project);
+
+ // populate metadata if existing table
+ for (const base of await project.getBases()) {
+ const info = await populateMeta(base, project);
+
+ T.emit('evt_api_created', info);
+ delete base.config;
+ }
+
+ T.emit('evt', {
+ evt_type: 'project:created',
+ xcdb: !projectBody.external,
+ });
+
+ T.emit('evt', { evt_type: 'project:rest' });
+
+ return project;
+}
+
+const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4);
+
+import { T } from 'nc-help';
+
+export async function getProjectWithInfo(param: { projectId: string }) {
+ const project = await Project.getWithInfo(param.projectId);
+ return project;
+}
+
+export async function projectSoftDelete(param: { projectId: any }) {
+ await Project.softDelete(param.projectId);
+ T.emit('evt', { evt_type: 'project:deleted' });
+ return true;
+}
+
+export function sanitizeProject(project: any) {
+ const sanitizedProject = { ...project };
+ sanitizedProject.bases?.forEach((b: any) => {
+ ['config'].forEach((k) => delete b[k]);
+ });
+ return sanitizedProject;
+}
+
+export async function projectUpdate(param: {
+ projectId: string;
+ project: ProjectReqType;
+}) {
+ const project = await Project.getWithInfo(param.projectId);
+
+ const data: Partial = extractPropsAndSanitize(
+ param?.project as Project,
+ ['title', 'meta', 'color']
+ );
+
+ if (
+ data?.title &&
+ project.title !== data.title &&
+ (await Project.getByTitle(data.title))
+ ) {
+ NcError.badRequest('Project title already in use');
+ }
+
+ const result = await Project.update(param.projectId, data);
+ T.emit('evt', { evt_type: 'project:update' });
+
+ return result;
+}
+
+export async function projectList(param: {
+ user: { id: string; roles: string };
+ query?: any;
+}) {
+ const projects = param.user?.roles?.includes(OrgUserRoles.SUPER_ADMIN)
+ ? await Project.list(param.query)
+ : await ProjectUser.getProjectsList(param.user.id, param.query);
+
+ return projects;
+}
diff --git a/packages/nocodb/src/lib/services/projectUserService.ts b/packages/nocodb/src/lib/services/projectUserService.ts
new file mode 100644
index 0000000000..675ba744a6
--- /dev/null
+++ b/packages/nocodb/src/lib/services/projectUserService.ts
@@ -0,0 +1,325 @@
+import { OrgUserRoles, ProjectUserReqType } from 'nocodb-sdk';
+import { T } from 'nc-help';
+import { validatePayload } from '../meta/api/helpers';
+import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
+import ProjectUser from '../models/ProjectUser';
+import validator from 'validator';
+import { NcError } from '../meta/helpers/catchError';
+import { v4 as uuidv4 } from 'uuid';
+import User from '../models/User';
+import Audit from '../models/Audit';
+import NocoCache from '../cache/NocoCache';
+import { CacheGetType, CacheScope, MetaTable } from '../utils/globals';
+import * as ejs from 'ejs';
+import NcPluginMgrv2 from '../meta/helpers/NcPluginMgrv2';
+import Noco from '../Noco';
+import { PluginCategory } from 'nocodb-sdk';
+import { randomTokenString } from '../meta/helpers/stringHelpers';
+
+export async function userList(param: { projectId: string; query: any }) {
+ return new PagedResponseImpl(
+ await ProjectUser.getUsersList({
+ ...param.query,
+ project_id: param.projectId,
+ }),
+ {
+ ...param.query,
+ count: await ProjectUser.getUsersCount(param.query),
+ }
+ );
+}
+
+export async function userInvite(param: {
+ projectId: string;
+ projectUser: ProjectUserReqType;
+ req: any;
+}): Promise {
+ validatePayload(
+ 'swagger.json#/components/schemas/ProjectUserReq',
+ param.projectUser
+ );
+
+ const emails = (param.projectUser.email || '')
+ .toLowerCase()
+ .split(/\s*,\s*/)
+ .map((v) => v.trim());
+
+ // check for invalid emails
+ const invalidEmails = emails.filter((v) => !validator.isEmail(v));
+ if (!emails.length) {
+ return NcError.badRequest('Invalid email address');
+ }
+ if (invalidEmails.length) {
+ NcError.badRequest('Invalid email address : ' + invalidEmails.join(', '));
+ }
+
+ const invite_token = uuidv4();
+ const error = [];
+
+ for (const email of emails) {
+ // add user to project if user already exist
+ const user = await User.getByEmail(email);
+
+ if (user) {
+ // check if this user has been added to this project
+ const projectUser = await ProjectUser.get(param.projectId, user.id);
+ if (projectUser) {
+ NcError.badRequest(
+ `${user.email} with role ${projectUser.roles} already exists in this project`
+ );
+ }
+
+ await ProjectUser.insert({
+ project_id: param.projectId,
+ fk_user_id: user.id,
+ roles: param.projectUser.roles || 'editor',
+ });
+
+ const cachedUser = await NocoCache.get(
+ `${CacheScope.USER}:${email}___${param.projectId}`,
+ CacheGetType.TYPE_OBJECT
+ );
+
+ if (cachedUser) {
+ cachedUser.roles = param.projectUser.roles || 'editor';
+ await NocoCache.set(
+ `${CacheScope.USER}:${email}___${param.projectId}`,
+ cachedUser
+ );
+ }
+
+ await Audit.insert({
+ project_id: param.projectId,
+ op_type: 'AUTHENTICATION',
+ op_sub_type: 'INVITE',
+ user: param.req.user.email,
+ description: `invited ${email} to ${param.projectId} project `,
+ ip: param.req.clientIp,
+ });
+ } else {
+ try {
+ // create new user with invite token
+ const { id } = await User.insert({
+ invite_token,
+ invite_token_expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
+ email,
+ roles: OrgUserRoles.VIEWER,
+ token_version: randomTokenString(),
+ });
+
+ // add user to project
+ await ProjectUser.insert({
+ project_id: param.projectId,
+ fk_user_id: id,
+ roles: param.projectUser.roles,
+ });
+
+ const count = await User.count();
+ T.emit('evt', { evt_type: 'project:invite', count });
+
+ await Audit.insert({
+ project_id: param.projectId,
+ op_type: 'AUTHENTICATION',
+ op_sub_type: 'INVITE',
+ user: param.req.user.email,
+ description: `invited ${email} to ${param.projectId} project `,
+ ip: param.req.clientIp,
+ });
+ // in case of single user check for smtp failure
+ // and send back token if failed
+ if (
+ emails.length === 1 &&
+ !(await sendInviteEmail(email, invite_token, param.req))
+ ) {
+ return { invite_token, email };
+ } else {
+ sendInviteEmail(email, invite_token, param.req);
+ }
+ } catch (e) {
+ console.log(e);
+ if (emails.length === 1) {
+ throw e;
+ } else {
+ error.push({ email, error: e.message });
+ }
+ }
+ }
+ }
+
+ if (emails.length === 1) {
+ return {
+ msg: 'success',
+ };
+ } else {
+ return { invite_token, emails, error };
+ }
+}
+
+export async function projectUserUpdate(param: {
+ userId: string;
+ // todo: update swagger
+ projectUser: ProjectUserReqType & { project_id: string };
+ // todo: refactor
+ req: any;
+ projectId: string;
+}): Promise {
+ validatePayload(
+ 'swagger.json#/components/schemas/ProjectUserReq',
+ param.projectUser
+ );
+
+ // todo: use param.projectId
+ if (!param.projectUser?.project_id) {
+ NcError.badRequest('Missing project id in request body.');
+ }
+
+ if (
+ param.req.session?.passport?.user?.roles?.owner &&
+ param.req.session?.passport?.user?.id === param.userId &&
+ param.projectUser.roles.indexOf('owner') === -1
+ ) {
+ NcError.badRequest("Super admin can't remove Super role themselves");
+ }
+ const user = await User.get(param.userId);
+
+ if (!user) {
+ NcError.badRequest(`User with id '${param.userId}' doesn't exist`);
+ }
+
+ // todo: handle roles which contains super
+ if (
+ !param.req.session?.passport?.user?.roles?.owner &&
+ param.projectUser.roles.indexOf('owner') > -1
+ ) {
+ NcError.forbidden('Insufficient privilege to add super admin role.');
+ }
+
+ await ProjectUser.update(
+ param.projectId,
+ param.userId,
+ param.projectUser.roles
+ );
+
+ await Audit.insert({
+ op_type: 'AUTHENTICATION',
+ op_sub_type: 'ROLES_MANAGEMENT',
+ user: param.req.user.email,
+ description: `updated roles for ${user.email} with ${param.projectUser.roles} `,
+ ip: param.req.clientIp,
+ });
+
+ return {
+ msg: 'User details updated successfully',
+ };
+}
+
+export async function projectUserDelete(param: {
+ projectId: string;
+ userId: string;
+ // todo: refactor
+ req: any;
+}): Promise {
+ const project_id = param.projectId;
+
+ if (param.req.session?.passport?.user?.id === param.userId) {
+ NcError.badRequest("Admin can't delete themselves!");
+ }
+
+ if (!param.req.session?.passport?.user?.roles?.owner) {
+ const user = await User.get(param.userId);
+ if (user.roles?.split(',').includes('super'))
+ NcError.forbidden('Insufficient privilege to delete a super admin user.');
+
+ const projectUser = await ProjectUser.get(project_id, param.userId);
+ if (projectUser?.roles?.split(',').includes('super'))
+ NcError.forbidden('Insufficient privilege to delete a owner user.');
+ }
+
+ await ProjectUser.delete(project_id, param.userId);
+ return true;
+}
+
+export async function projectUserInviteResend(param: {
+ userId: string;
+ projectUser: ProjectUserReqType;
+ projectId: string;
+ // todo: refactor
+ req: any;
+}): Promise {
+ const user = await User.get(param.userId);
+
+ if (!user) {
+ NcError.badRequest(`User with id '${param.userId}' not found`);
+ }
+
+ param.projectUser.roles = user.roles;
+ const invite_token = uuidv4();
+
+ await User.update(user.id, {
+ invite_token,
+ invite_token_expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
+ });
+
+ const pluginData = await Noco.ncMeta.metaGet2(null, null, MetaTable.PLUGIN, {
+ category: PluginCategory.EMAIL,
+ active: true,
+ });
+
+ if (!pluginData) {
+ NcError.badRequest(
+ `No Email Plugin is found. Please go to App Store to configure first or copy the invitation URL to users instead.`
+ );
+ }
+
+ await sendInviteEmail(user.email, invite_token, param.req);
+
+ await Audit.insert({
+ op_type: 'AUTHENTICATION',
+ op_sub_type: 'RESEND_INVITE',
+ user: user.email,
+ description: `resent a invite to ${user.email} `,
+ ip: param.req.clientIp,
+ project_id: param.projectId,
+ });
+
+ return true;
+}
+
+// todo: refactor the whole function
+export async function sendInviteEmail(
+ email: string,
+ token: string,
+ req: any
+): Promise {
+ try {
+ const template = (await import('./userService/ui/emailTemplates/invite'))
+ .default;
+
+ const emailAdapter = await NcPluginMgrv2.emailAdapter();
+
+ if (emailAdapter) {
+ await emailAdapter.mailSend({
+ to: email,
+ subject: 'Verify email',
+ html: ejs.render(template, {
+ signupLink: `${req.ncSiteUrl}${
+ Noco.getConfig()?.dashboardPath
+ }#/signup/${token}`,
+ projectName: req.body?.projectName,
+ roles: (req.body?.roles || '')
+ .split(',')
+ .map((r) => r.replace(/^./, (m) => m.toUpperCase()))
+ .join(', '),
+ adminEmail: req.session?.passport?.user?.email,
+ }),
+ });
+ return true;
+ }
+ } catch (e) {
+ console.log(
+ 'Warning : `mailSend` failed, Please configure emailClient configuration.',
+ e.message
+ );
+ throw e;
+ }
+}
diff --git a/packages/nocodb/src/lib/services/public/index.ts b/packages/nocodb/src/lib/services/public/index.ts
new file mode 100644
index 0000000000..b24c8caab5
--- /dev/null
+++ b/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 };
diff --git a/packages/nocodb/src/lib/services/public/publicDataExportService.ts b/packages/nocodb/src/lib/services/public/publicDataExportService.ts
new file mode 100644
index 0000000000..900dd3ed88
--- /dev/null
+++ b/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();
+ 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();
+ 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;
+ }
+}
diff --git a/packages/nocodb/src/lib/services/public/publicDataService.ts b/packages/nocodb/src/lib/services/public/publicDataService.ts
new file mode 100644
index 0000000000..238f5e5825
--- /dev/null
+++ b/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();
+
+ 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 });
+}
diff --git a/packages/nocodb/src/lib/meta/api/publicApis/publicMetaApis.ts b/packages/nocodb/src/lib/services/public/publicMetaservice.ts
similarity index 65%
rename from packages/nocodb/src/lib/meta/api/publicApis/publicMetaApis.ts
rename to packages/nocodb/src/lib/services/public/publicMetaservice.ts
index 9bb4aaf946..940b03e877 100644
--- a/packages/nocodb/src/lib/meta/api/publicApis/publicMetaApis.ts
+++ b/packages/nocodb/src/lib/services/public/publicMetaservice.ts
@@ -1,27 +1,31 @@
-import { Request, Response, Router } from 'express';
-import catchError, { NcError } from '../../helpers/catchError';
-import View from '../../../models/View';
-import Model from '../../../models/Model';
import {
ErrorMessages,
LinkToAnotherRecordType,
RelationTypes,
UITypes,
} from 'nocodb-sdk';
-import Column from '../../../models/Column';
-import Base from '../../../models/Base';
-import Project from '../../../models/Project';
-import LinkToAnotherRecordColumn from '../../../models/LinkToAnotherRecordColumn';
-
-export async function viewMetaGet(req: Request, res: Response) {
+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(req.params.sharedViewUuid);
+ } = await View.getByUUID(param.sharedViewUuid);
if (!view) NcError.notFound('Not found');
- if (view.password && view.password !== req.headers?.['xc-password']) {
+ if (view.password && view.password !== param.password) {
NcError.forbidden(ErrorMessages.INVALID_SHARED_VIEW_PASSWORD);
}
@@ -81,26 +85,16 @@ export async function viewMetaGet(req: Request, res: Response) {
view.relatedMetas = relatedMetas;
- res.json(view);
+ return view;
}
-async function publicSharedBaseGet(req, res): Promise {
- const project = await Project.getByUuid(req.params.sharedBaseUuid);
+export async function publicSharedBaseGet(param: {
+ sharedBaseUuid: string;
+}): Promise {
+ const project = await Project.getByUuid(param.sharedBaseUuid);
if (!project) {
NcError.notFound();
}
- res.json({ project_id: project.id });
+ return { project_id: project.id };
}
-
-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;
diff --git a/packages/nocodb/src/lib/services/sharedBaseService.ts b/packages/nocodb/src/lib/services/sharedBaseService.ts
new file mode 100644
index 0000000000..a3549781f4
--- /dev/null
+++ b/packages/nocodb/src/lib/services/sharedBaseService.ts
@@ -0,0 +1,112 @@
+import { T } from 'nc-help';
+import { v4 as uuidv4 } from 'uuid';
+import { validatePayload } from '../meta/api/helpers';
+import Project from '../models/Project';
+import { NcError } from '../meta/helpers/catchError';
+// todo: load from config
+const config = {
+ dashboardPath: '/nc',
+};
+
+export async function createSharedBaseLink(param: {
+ projectId: string;
+ roles: string;
+ password: string;
+ siteUrl: string;
+}): Promise {
+ validatePayload('swagger.json#/components/schemas/SharedBaseReq', param);
+
+ const project = await Project.get(param.projectId);
+
+ let roles = param?.roles;
+ if (!roles || (roles !== 'editor' && roles !== 'viewer')) {
+ roles = 'viewer';
+ }
+
+ if (!project) {
+ NcError.badRequest('Invalid project id');
+ }
+
+ const data: any = {
+ uuid: uuidv4(),
+ password: param?.password,
+ roles,
+ };
+
+ await Project.update(project.id, data);
+
+ data.url = `${param.siteUrl}${config.dashboardPath}#/nc/base/${data.uuid}`;
+ delete data.password;
+ T.emit('evt', { evt_type: 'sharedBase:generated-link' });
+ return data;
+}
+
+export async function updateSharedBaseLink(param: {
+ projectId: string;
+ roles: string;
+ password: string;
+ siteUrl: string;
+}): Promise {
+ validatePayload('swagger.json#/components/schemas/SharedBaseReq', param);
+
+ const project = await Project.get(param.projectId);
+
+ let roles = param.roles;
+ if (!roles || (roles !== 'editor' && roles !== 'viewer')) {
+ roles = 'viewer';
+ }
+
+ if (!project) {
+ NcError.badRequest('Invalid project id');
+ }
+ const data: any = {
+ uuid: project.uuid || uuidv4(),
+ password: param.password,
+ roles,
+ };
+
+ await Project.update(project.id, data);
+
+ data.url = `${param.siteUrl}${config.dashboardPath}#/nc/base/${data.uuid}`;
+ delete data.password;
+ T.emit('evt', { evt_type: 'sharedBase:generated-link' });
+ return data;
+}
+
+export async function disableSharedBaseLink(param: {
+ projectId: string;
+}): Promise {
+ const project = await Project.get(param.projectId);
+
+ if (!project) {
+ NcError.badRequest('Invalid project id');
+ }
+ const data: any = {
+ uuid: null,
+ };
+
+ await Project.update(project.id, data);
+
+ T.emit('evt', { evt_type: 'sharedBase:disable-link' });
+
+ return { uuid: null };
+}
+
+export async function getSharedBaseLink(param: {
+ projectId: string;
+ siteUrl: string;
+}): Promise {
+ const project = await Project.get(param.projectId);
+
+ if (!project) {
+ NcError.badRequest('Invalid project id');
+ }
+ const data: any = {
+ uuid: project.uuid,
+ roles: project.roles,
+ };
+ if (data.uuid)
+ data.url = `${param.siteUrl}${config.dashboardPath}#/nc/base/${data.shared_base_id}`;
+
+ return data;
+}
diff --git a/packages/nocodb/src/lib/services/sortService.ts b/packages/nocodb/src/lib/services/sortService.ts
new file mode 100644
index 0000000000..c2d05948c1
--- /dev/null
+++ b/packages/nocodb/src/lib/services/sortService.ts
@@ -0,0 +1,37 @@
+import { SortReqType } from 'nocodb-sdk';
+import { validatePayload } from '../meta/api/helpers';
+import Sort from '../models/Sort';
+import { T } from 'nc-help';
+
+export async function sortGet(param: { sortId: string }) {
+ return Sort.get(param.sortId);
+}
+
+export async function sortDelete(param: { sortId: string }) {
+ await Sort.delete(param.sortId);
+ T.emit('evt', { evt_type: 'sort:deleted' });
+ return true;
+}
+
+export async function sortUpdate(param: { sortId: any; sort: SortReqType }) {
+ validatePayload('swagger.json#/components/schemas/SortReq', param.sort);
+
+ const sort = await Sort.update(param.sortId, param.sort);
+ T.emit('evt', { evt_type: 'sort:updated' });
+ return sort;
+}
+
+export async function sortCreate(param: { viewId: any; sort: SortReqType }) {
+ validatePayload('swagger.json#/components/schemas/SortReq', param.sort);
+
+ const sort = await Sort.insert({
+ ...param.sort,
+ fk_view_id: param.viewId,
+ } as Sort);
+ T.emit('evt', { evt_type: 'sort:created' });
+ return sort;
+}
+
+export async function sortList(param: { viewId: string }) {
+ return Sort.list({ viewId: param.viewId });
+}
diff --git a/packages/nocodb/src/lib/meta/api/swagger/helpers/getPaths.ts b/packages/nocodb/src/lib/services/swaggerService/getPaths.ts
similarity index 88%
rename from packages/nocodb/src/lib/meta/api/swagger/helpers/getPaths.ts
rename to packages/nocodb/src/lib/services/swaggerService/getPaths.ts
index a1d3d0b8f8..042ef515d6 100644
--- a/packages/nocodb/src/lib/meta/api/swagger/helpers/getPaths.ts
+++ b/packages/nocodb/src/lib/services/swaggerService/getPaths.ts
@@ -1,6 +1,6 @@
-import Noco from '../../../../Noco';
-import Model from '../../../../models/Model';
-import Project from '../../../../models/Project';
+import Noco from '../../Noco';
+import Model from '../../models/Model';
+import Project from '../../models/Project';
import { getModelPaths, getViewPaths } from './templates/paths';
import { SwaggerColumn } from './getSwaggerColumnMetas';
import { SwaggerView } from './getSwaggerJSON';
diff --git a/packages/nocodb/src/lib/meta/api/swagger/helpers/getSchemas.ts b/packages/nocodb/src/lib/services/swaggerService/getSchemas.ts
similarity index 88%
rename from packages/nocodb/src/lib/meta/api/swagger/helpers/getSchemas.ts
rename to packages/nocodb/src/lib/services/swaggerService/getSchemas.ts
index a62d19b220..5470c19fa9 100644
--- a/packages/nocodb/src/lib/meta/api/swagger/helpers/getSchemas.ts
+++ b/packages/nocodb/src/lib/services/swaggerService/getSchemas.ts
@@ -1,6 +1,6 @@
-import Noco from '../../../../Noco';
-import Model from '../../../../models/Model';
-import Project from '../../../../models/Project';
+import Noco from '../../Noco';
+import Model from '../../models/Model';
+import Project from '../../models/Project';
import { getModelSchemas, getViewSchemas } from './templates/schemas';
import { SwaggerColumn } from './getSwaggerColumnMetas';
import { SwaggerView } from './getSwaggerJSON';
diff --git a/packages/nocodb/src/lib/meta/api/swagger/helpers/getSwaggerColumnMetas.ts b/packages/nocodb/src/lib/services/swaggerService/getSwaggerColumnMetas.ts
similarity index 83%
rename from packages/nocodb/src/lib/meta/api/swagger/helpers/getSwaggerColumnMetas.ts
rename to packages/nocodb/src/lib/services/swaggerService/getSwaggerColumnMetas.ts
index 6ea1b312d6..45b76e4280 100644
--- a/packages/nocodb/src/lib/meta/api/swagger/helpers/getSwaggerColumnMetas.ts
+++ b/packages/nocodb/src/lib/services/swaggerService/getSwaggerColumnMetas.ts
@@ -1,9 +1,9 @@
import { UITypes } from 'nocodb-sdk';
-import LinkToAnotherRecordColumn from '../../../../models/LinkToAnotherRecordColumn';
-import SwaggerTypes from '../../../../db/sql-mgr/code/routers/xc-ts/SwaggerTypes';
-import Column from '../../../../models/Column';
-import Noco from '../../../../Noco';
-import Project from '../../../../models/Project';
+import LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn';
+import SwaggerTypes from '../../db/sql-mgr/code/routers/xc-ts/SwaggerTypes';
+import Column from '../../models/Column';
+import Noco from '../../Noco';
+import Project from '../../models/Project';
export default async (
columns: Column[],
diff --git a/packages/nocodb/src/lib/meta/api/swagger/helpers/getSwaggerJSON.ts b/packages/nocodb/src/lib/services/swaggerService/getSwaggerJSON.ts
similarity index 80%
rename from packages/nocodb/src/lib/meta/api/swagger/helpers/getSwaggerJSON.ts
rename to packages/nocodb/src/lib/services/swaggerService/getSwaggerJSON.ts
index 19c09a5514..8d4e81f478 100644
--- a/packages/nocodb/src/lib/meta/api/swagger/helpers/getSwaggerJSON.ts
+++ b/packages/nocodb/src/lib/services/swaggerService/getSwaggerJSON.ts
@@ -1,15 +1,13 @@
-import FormViewColumn from '../../../../models/FormViewColumn';
-import GalleryViewColumn from '../../../../models/GalleryViewColumn';
-import Noco from '../../../../Noco';
-import Model from '../../../../models/Model';
+import { Model, Project, View } from '../../models';
+import FormViewColumn from '../../models/FormViewColumn';
+import GalleryViewColumn from '../../models/GalleryViewColumn';
+import Noco from '../../Noco';
import swaggerBase from './swagger-base.json';
import getPaths from './getPaths';
import getSchemas from './getSchemas';
-import Project from '../../../../models/Project';
import getSwaggerColumnMetas from './getSwaggerColumnMetas';
import { ViewTypes } from 'nocodb-sdk';
-import GridViewColumn from '../../../../models/GridViewColumn';
-import View from '../../../../models/View';
+import GridViewColumn from '../../models/GridViewColumn';
export default async function getSwaggerJSON(
project: Project,
diff --git a/packages/nocodb/src/lib/services/swaggerService/index.ts b/packages/nocodb/src/lib/services/swaggerService/index.ts
new file mode 100644
index 0000000000..4b46830bf4
--- /dev/null
+++ b/packages/nocodb/src/lib/services/swaggerService/index.ts
@@ -0,0 +1,37 @@
+import { NcError } from '../../meta/helpers/catchError';
+import Model from '../../models/Model';
+import Project from '../../models/Project';
+import getSwaggerJSON from './getSwaggerJSON';
+
+export async function swaggerJson(param: {
+ projectId: string;
+ siteUrl: string;
+}) {
+ const project = await Project.get(param.projectId);
+
+ if (!project) NcError.notFound();
+
+ const models = await Model.list({
+ project_id: param.projectId,
+ base_id: null,
+ });
+
+ const swagger = await getSwaggerJSON(project, models);
+
+ swagger.servers = [
+ {
+ url: param.siteUrl,
+ },
+ {
+ url: '{customUrl}',
+ variables: {
+ customUrl: {
+ default: param.siteUrl,
+ description: 'Provide custom nocodb app base url',
+ },
+ },
+ },
+ ] as any;
+
+ return swagger;
+}
diff --git a/packages/nocodb/src/lib/meta/api/swagger/helpers/swagger-base.json b/packages/nocodb/src/lib/services/swaggerService/swagger-base.json
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/swagger/helpers/swagger-base.json
rename to packages/nocodb/src/lib/services/swaggerService/swagger-base.json
diff --git a/packages/nocodb/src/lib/meta/api/swagger/helpers/templates/headers.ts b/packages/nocodb/src/lib/services/swaggerService/templates/headers.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/swagger/helpers/templates/headers.ts
rename to packages/nocodb/src/lib/services/swaggerService/templates/headers.ts
diff --git a/packages/nocodb/src/lib/meta/api/swagger/helpers/templates/params.ts b/packages/nocodb/src/lib/services/swaggerService/templates/params.ts
similarity index 98%
rename from packages/nocodb/src/lib/meta/api/swagger/helpers/templates/params.ts
rename to packages/nocodb/src/lib/services/swaggerService/templates/params.ts
index 891ecfd10f..698dbc5eb5 100644
--- a/packages/nocodb/src/lib/meta/api/swagger/helpers/templates/params.ts
+++ b/packages/nocodb/src/lib/services/swaggerService/templates/params.ts
@@ -1,6 +1,6 @@
import { SwaggerColumn } from '../getSwaggerColumnMetas';
import { RelationTypes, UITypes } from 'nocodb-sdk';
-import LinkToAnotherRecordColumn from '../../../../../models/LinkToAnotherRecordColumn';
+import LinkToAnotherRecordColumn from '../../../models/LinkToAnotherRecordColumn';
export const rowIdParam = {
schema: {
diff --git a/packages/nocodb/src/lib/meta/api/swagger/helpers/templates/paths.ts b/packages/nocodb/src/lib/services/swaggerService/templates/paths.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/swagger/helpers/templates/paths.ts
rename to packages/nocodb/src/lib/services/swaggerService/templates/paths.ts
diff --git a/packages/nocodb/src/lib/meta/api/swagger/helpers/templates/schemas.ts b/packages/nocodb/src/lib/services/swaggerService/templates/schemas.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/swagger/helpers/templates/schemas.ts
rename to packages/nocodb/src/lib/services/swaggerService/templates/schemas.ts
diff --git a/packages/nocodb/src/lib/meta/api/sync/helpers/EntityMap.ts b/packages/nocodb/src/lib/services/syncService/helpers/EntityMap.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/sync/helpers/EntityMap.ts
rename to packages/nocodb/src/lib/services/syncService/helpers/EntityMap.ts
diff --git a/packages/nocodb/src/lib/meta/api/sync/helpers/NocoSyncDestAdapter.ts b/packages/nocodb/src/lib/services/syncService/helpers/NocoSyncDestAdapter.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/sync/helpers/NocoSyncDestAdapter.ts
rename to packages/nocodb/src/lib/services/syncService/helpers/NocoSyncDestAdapter.ts
diff --git a/packages/nocodb/src/lib/meta/api/sync/helpers/NocoSyncSourceAdapter.ts b/packages/nocodb/src/lib/services/syncService/helpers/NocoSyncSourceAdapter.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/sync/helpers/NocoSyncSourceAdapter.ts
rename to packages/nocodb/src/lib/services/syncService/helpers/NocoSyncSourceAdapter.ts
diff --git a/packages/nocodb/src/lib/meta/api/sync/helpers/fetchAT.ts b/packages/nocodb/src/lib/services/syncService/helpers/fetchAT.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/sync/helpers/fetchAT.ts
rename to packages/nocodb/src/lib/services/syncService/helpers/fetchAT.ts
diff --git a/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts b/packages/nocodb/src/lib/services/syncService/helpers/job.ts
similarity index 98%
rename from packages/nocodb/src/lib/meta/api/sync/helpers/job.ts
rename to packages/nocodb/src/lib/services/syncService/helpers/job.ts
index 84ea23d1db..3f09d188ad 100644
--- a/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts
+++ b/packages/nocodb/src/lib/services/syncService/helpers/job.ts
@@ -1,4 +1,4 @@
-import { Tele } from 'nc-help';
+import { T } from 'nc-help';
import FetchAT from './fetchAT';
import { UITypes } from 'nocodb-sdk';
// import * as sMap from './syncMap';
@@ -11,6 +11,7 @@ import hash from 'object-hash';
import { promisify } from 'util';
import dayjs from 'dayjs';
+import utc from 'dayjs/plugin/utc';
import tinycolor from 'tinycolor2';
import { importData, importLTARData } from './readAndProcessData';
@@ -18,6 +19,8 @@ import EntityMap from './EntityMap';
const writeJsonFileAsync = promisify(jsonfile.writeFile);
+dayjs.extend(utc);
+
const selectColors = {
// normal
blue: '#cfdfff',
@@ -909,6 +912,8 @@ export default async (
aTblLinkColumns[i].name + suffix,
ncTbl.id
);
+
+ // console.log(res.columns.find(x => x.title === aTblLinkColumns[i].name))
}
}
}
@@ -1497,6 +1502,8 @@ export default async (
})
.eachPage(
async function page(records, fetchNextPage) {
+ // console.log(JSON.stringify(records, null, 2));
+
// This function (`page`) will get called for each page of records.
// records.forEach(record => callback(table, record));
logBasic(
@@ -1924,7 +1931,7 @@ export default async (
});
}
- Tele.event({
+ T.event({
event: 'a:airtable-import:success',
data: {
stats: {
@@ -1965,7 +1972,6 @@ export default async (
'>=': 'gte',
isEmpty: 'empty',
isNotEmpty: 'notempty',
- isWithin: 'isWithin',
contains: 'like',
doesNotContain: 'nlike',
isAnyOf: 'anyof',
@@ -1994,29 +2000,16 @@ export default async (
const datatype = colSchema.uidt;
const ncFilters = [];
+ // console.log(filter)
if (datatype === UITypes.Date || datatype === UITypes.DateTime) {
- let comparison_op = null;
- let comparison_sub_op = null;
- let value = null;
- if (['isEmpty', 'isNotEmpty'].includes(filter.operator)) {
- comparison_op = filter.operator === 'isEmpty' ? 'blank' : 'notblank';
- } else {
- if ('numberOfDays' in filter.value) {
- value = filter.value['numberOfDays'];
- } else if ('exactDate' in filter.value) {
- value = filter.value['exactDate'];
- }
- comparison_op = filterMap[filter.operator];
- comparison_sub_op = filter.value.mode;
- }
- const fx = {
- fk_column_id: columnId,
- logical_op: f.conjunction,
- comparison_op,
- comparison_sub_op,
- value,
- };
- ncFilters.push(fx);
+ // skip filters over data datatype
+ updateMigrationSkipLog(
+ await sMap.getNcNameFromAtId(viewId),
+ colSchema.title,
+ colSchema.uidt,
+ `filter config skipped; filter over date datatype not supported`
+ );
+ continue;
}
// single-select & multi-select
@@ -2391,7 +2384,7 @@ export default async (
}
} catch (e) {
if (e.response?.data?.msg) {
- Tele.event({
+ T.event({
event: 'a:airtable-import:error',
data: { error: e.response.data.msg },
});
diff --git a/packages/nocodb/src/lib/meta/api/sync/helpers/readAndProcessData.ts b/packages/nocodb/src/lib/services/syncService/helpers/readAndProcessData.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/sync/helpers/readAndProcessData.ts
rename to packages/nocodb/src/lib/services/syncService/helpers/readAndProcessData.ts
diff --git a/packages/nocodb/src/lib/meta/api/sync/helpers/syncMap.ts b/packages/nocodb/src/lib/services/syncService/helpers/syncMap.ts
similarity index 100%
rename from packages/nocodb/src/lib/meta/api/sync/helpers/syncMap.ts
rename to packages/nocodb/src/lib/services/syncService/helpers/syncMap.ts
diff --git a/packages/nocodb/src/lib/services/syncService/index.ts b/packages/nocodb/src/lib/services/syncService/index.ts
new file mode 100644
index 0000000000..1c1f17b177
--- /dev/null
+++ b/packages/nocodb/src/lib/services/syncService/index.ts
@@ -0,0 +1,47 @@
+import { T } from 'nc-help';
+import { PagedResponseImpl } from '../../meta/helpers/PagedResponse';
+import { Project, SyncSource } from '../../models';
+
+export async function syncSourceList(param: {
+ projectId: string;
+ baseId?: string;
+}) {
+ return new PagedResponseImpl(
+ await SyncSource.list(param.projectId, param.baseId)
+ );
+}
+
+export async function syncCreate(param: {
+ projectId: string;
+ baseId?: string;
+ userId: string;
+ // todo: define type
+ syncPayload: Partial;
+}) {
+ T.emit('evt', { evt_type: 'webhooks:created' });
+ const project = await Project.getWithInfo(param.projectId);
+
+ const sync = await SyncSource.insert({
+ ...param.syncPayload,
+ fk_user_id: param.userId,
+ base_id: param.baseId ? param.baseId : project.bases[0].id,
+ project_id: param.projectId,
+ });
+ return sync;
+}
+
+export async function syncDelete(param: { syncId: string }) {
+ T.emit('evt', { evt_type: 'webhooks:deleted' });
+ return await SyncSource.delete(param.syncId);
+}
+
+export async function syncUpdate(param: {
+ syncId: string;
+ syncPayload: Partial;
+}) {
+ T.emit('evt', { evt_type: 'webhooks:updated' });
+
+ return await SyncSource.update(param.syncId, param.syncPayload);
+}
+
+export { default as airtableImportJob } from './helpers/job';
diff --git a/packages/nocodb/src/lib/services/tableService.ts b/packages/nocodb/src/lib/services/tableService.ts
new file mode 100644
index 0000000000..747f2d57b8
--- /dev/null
+++ b/packages/nocodb/src/lib/services/tableService.ts
@@ -0,0 +1,464 @@
+import DOMPurify from 'isomorphic-dompurify';
+import {
+ AuditOperationSubTypes,
+ AuditOperationTypes,
+ isVirtualCol,
+ ModelTypes,
+ NormalColumnRequestType,
+ TableReqType,
+ UITypes,
+} from 'nocodb-sdk';
+import ProjectMgrv2 from '../db/sql-mgr/v2/ProjectMgrv2';
+import { validatePayload } from '../meta/api/helpers';
+import { NcError } from '../meta/helpers/catchError';
+import getColumnPropsFromUIDT from '../meta/helpers/getColumnPropsFromUIDT';
+import getColumnUiType from '../meta/helpers/getColumnUiType';
+import getTableNameAlias, {
+ getColumnNameAlias,
+} from '../meta/helpers/getTableName';
+import mapDefaultDisplayValue from '../meta/helpers/mapDefaultDisplayValue';
+import {
+ Audit,
+ Column,
+ LinkToAnotherRecordColumn,
+ Model,
+ ModelRoleVisibility,
+ Project,
+ User,
+ View,
+} from '../models';
+import NcConnectionMgrv2 from '../utils/common/NcConnectionMgrv2';
+import { T } from 'nc-help';
+
+export async function tableUpdate(param: {
+ tableId: any;
+ table: TableReqType & { project_id?: string };
+ projectId?: string;
+}) {
+ const model = await Model.get(param.tableId);
+
+ const project = await Project.getWithInfo(
+ param.table.project_id || param.projectId
+ );
+ const base = project.bases.find((b) => b.id === model.base_id);
+
+ if (model.project_id !== project.id) {
+ NcError.badRequest('Model does not belong to project');
+ }
+
+ // if meta present update meta and return
+ // todo: allow user to update meta and other prop in single api call
+ if ('meta' in param.table) {
+ await Model.updateMeta(param.tableId, param.table.meta);
+
+ return true;
+ }
+
+ if (!param.table.table_name) {
+ NcError.badRequest(
+ 'Missing table name `table_name` property in request body'
+ );
+ }
+
+ if (base.is_meta && project.prefix) {
+ if (!param.table.table_name.startsWith(project.prefix)) {
+ param.table.table_name = `${project.prefix}${param.table.table_name}`;
+ }
+ }
+
+ param.table.table_name = DOMPurify.sanitize(param.table.table_name);
+
+ // validate table name
+ if (/^\s+|\s+$/.test(param.table.table_name)) {
+ NcError.badRequest(
+ 'Leading or trailing whitespace not allowed in table names'
+ );
+ }
+
+ if (
+ !(await Model.checkTitleAvailable({
+ table_name: param.table.table_name,
+ project_id: project.id,
+ base_id: base.id,
+ }))
+ ) {
+ NcError.badRequest('Duplicate table name');
+ }
+
+ if (!param.table.title) {
+ param.table.title = getTableNameAlias(
+ param.table.table_name,
+ project.prefix,
+ base
+ );
+ }
+
+ if (
+ !(await Model.checkAliasAvailable({
+ title: param.table.title,
+ project_id: project.id,
+ base_id: base.id,
+ }))
+ ) {
+ NcError.badRequest('Duplicate table alias');
+ }
+
+ const sqlMgr = await ProjectMgrv2.getSqlMgr(project);
+ const sqlClient = await NcConnectionMgrv2.getSqlClient(base);
+
+ let tableNameLengthLimit = 255;
+ const sqlClientType = sqlClient.knex.clientType();
+ if (sqlClientType === 'mysql2' || sqlClientType === 'mysql') {
+ tableNameLengthLimit = 64;
+ } else if (sqlClientType === 'pg') {
+ tableNameLengthLimit = 63;
+ } else if (sqlClientType === 'mssql') {
+ tableNameLengthLimit = 128;
+ }
+
+ if (param.table.table_name.length > tableNameLengthLimit) {
+ NcError.badRequest(`Table name exceeds ${tableNameLengthLimit} characters`);
+ }
+
+ await Model.updateAliasAndTableName(
+ param.tableId,
+ param.table.title,
+ param.table.table_name
+ );
+
+ await sqlMgr.sqlOpPlus(base, 'tableRename', {
+ ...param.table,
+ tn: param.table.table_name,
+ tn_old: model.table_name,
+ });
+
+ T.emit('evt', { evt_type: 'table:updated' });
+ return true;
+}
+
+export function reorderTable(param: { tableId: string; order: any }) {
+ return Model.updateOrder(param.tableId, param.order);
+}
+
+export async function tableDelete(param: {
+ tableId: string;
+ user: User;
+ req?: any;
+}) {
+ const table = await Model.getByIdOrName({ id: param.tableId });
+ await table.getColumns();
+
+ const relationColumns = table.columns.filter(
+ (c) => c.uidt === UITypes.LinkToAnotherRecord
+ );
+
+ if (relationColumns?.length) {
+ const referredTables = await Promise.all(
+ relationColumns.map(async (c) =>
+ c
+ .getColOptions()
+ .then((opt) => opt.getRelatedTable())
+ .then()
+ )
+ );
+ NcError.badRequest(
+ `Table can't be deleted since Table is being referred in following tables : ${referredTables.join(
+ ', '
+ )}. Delete LinkToAnotherRecord columns and try again.`
+ );
+ }
+
+ const project = await Project.getWithInfo(table.project_id);
+ const base = project.bases.find((b) => b.id === table.base_id);
+ const sqlMgr = await ProjectMgrv2.getSqlMgr(project);
+ (table as any).tn = table.table_name;
+ table.columns = table.columns.filter((c) => !isVirtualCol(c));
+ table.columns.forEach((c) => {
+ (c as any).cn = c.column_name;
+ });
+
+ if (table.type === ModelTypes.TABLE) {
+ await sqlMgr.sqlOpPlus(base, 'tableDelete', table);
+ } else if (table.type === ModelTypes.VIEW) {
+ await sqlMgr.sqlOpPlus(base, 'viewDelete', {
+ ...table,
+ view_name: table.table_name,
+ });
+ }
+
+ await Audit.insert({
+ project_id: project.id,
+ base_id: base.id,
+ op_type: AuditOperationTypes.TABLE,
+ op_sub_type: AuditOperationSubTypes.DELETED,
+ user: param.user?.email,
+ description: `Deleted ${table.type} ${table.table_name} with alias ${table.title} `,
+ ip: param.req?.clientIp,
+ }).then(() => {});
+
+ T.emit('evt', { evt_type: 'table:deleted' });
+
+ return table.delete();
+}
+
+export async function getTableWithAccessibleViews(param: {
+ tableId: string;
+ user: User;
+}) {
+ const table = await Model.getWithInfo({
+ id: param.tableId,
+ });
+
+ // todo: optimise
+ const viewList = await xcVisibilityMetaGet(table.project_id, [table]);
+
+ //await View.list(param.tableId)
+ table.views = viewList.filter((table: any) => {
+ return Object.keys(param.user?.roles).some(
+ (role) => param.user?.roles[role] && !table.disabled[role]
+ );
+ });
+
+ return table;
+}
+
+export async function xcVisibilityMetaGet(
+ projectId,
+ _models: Model[] = null,
+ includeM2M = true
+ // type: 'table' | 'tableAndViews' | 'views' = 'table'
+) {
+ // todo: move to
+ const roles = ['owner', 'creator', 'viewer', 'editor', 'commenter', 'guest'];
+
+ const defaultDisabled = roles.reduce((o, r) => ({ ...o, [r]: false }), {});
+
+ let models =
+ _models ||
+ (await Model.list({
+ project_id: projectId,
+ base_id: undefined,
+ }));
+
+ models = includeM2M ? models : (models.filter((t) => !t.mm) as Model[]);
+
+ const result = await models.reduce(async (_obj, model) => {
+ const obj = await _obj;
+
+ const views = await model.getViews();
+ for (const view of views) {
+ obj[view.id] = {
+ ptn: model.table_name,
+ _ptn: model.title,
+ ptype: model.type,
+ tn: view.title,
+ _tn: view.title,
+ table_meta: model.meta,
+ ...view,
+ disabled: { ...defaultDisabled },
+ };
+ }
+
+ return obj;
+ }, Promise.resolve({}));
+
+ const disabledList = await ModelRoleVisibility.list(projectId);
+
+ for (const d of disabledList) {
+ if (result[d.fk_view_id])
+ result[d.fk_view_id].disabled[d.role] = !!d.disabled;
+ }
+
+ return Object.values(result);
+}
+
+export async function getAccessibleTables(param: {
+ projectId: string;
+ baseId: string;
+ includeM2M?: boolean;
+ roles: Record;
+}) {
+ const viewList = await xcVisibilityMetaGet(param.projectId);
+
+ // todo: optimise
+ const tableViewMapping = viewList.reduce((o, view: any) => {
+ o[view.fk_model_id] = o[view.fk_model_id] || 0;
+ if (
+ Object.keys(param.roles).some(
+ (role) => param.roles[role] && !view.disabled[role]
+ )
+ ) {
+ o[view.fk_model_id]++;
+ }
+ return o;
+ }, {});
+
+ const tableList = (
+ await Model.list({
+ project_id: param.projectId,
+ base_id: param.baseId,
+ })
+ ).filter((t) => tableViewMapping[t.id]);
+
+ return param.includeM2M
+ ? tableList
+ : (tableList.filter((t) => !t.mm) as Model[]);
+}
+
+export async function tableCreate(param: {
+ projectId: string;
+ baseId?: string;
+ table: TableReqType;
+ user: User;
+ req?: any;
+}) {
+ validatePayload('swagger.json#/components/schemas/TableReq', param.table);
+
+ const project = await Project.getWithInfo(param.projectId);
+ let base = project.bases[0];
+
+ if (param.baseId) {
+ base = project.bases.find((b) => b.id === param.baseId);
+ }
+
+ if (
+ !param.table.table_name ||
+ (project.prefix && project.prefix === param.table.table_name)
+ ) {
+ NcError.badRequest(
+ 'Missing table name `table_name` property in request body'
+ );
+ }
+
+ if (base.is_meta && project.prefix) {
+ if (!param.table.table_name.startsWith(project.prefix)) {
+ param.table.table_name = `${project.prefix}_${param.table.table_name}`;
+ }
+ }
+
+ param.table.table_name = DOMPurify.sanitize(param.table.table_name);
+
+ // validate table name
+ if (/^\s+|\s+$/.test(param.table.table_name)) {
+ NcError.badRequest(
+ 'Leading or trailing whitespace not allowed in table names'
+ );
+ }
+
+ if (
+ !(await Model.checkTitleAvailable({
+ table_name: param.table.table_name,
+ project_id: project.id,
+ base_id: base.id,
+ }))
+ ) {
+ NcError.badRequest('Duplicate table name');
+ }
+
+ if (!param.table.title) {
+ param.table.title = getTableNameAlias(
+ param.table.table_name,
+ project.prefix,
+ base
+ );
+ }
+
+ if (
+ !(await Model.checkAliasAvailable({
+ title: param.table.title,
+ project_id: project.id,
+ base_id: base.id,
+ }))
+ ) {
+ NcError.badRequest('Duplicate table alias');
+ }
+
+ const sqlMgr = await ProjectMgrv2.getSqlMgr(project);
+
+ const sqlClient = await NcConnectionMgrv2.getSqlClient(base);
+
+ let tableNameLengthLimit = 255;
+ const sqlClientType = sqlClient.knex.clientType();
+ if (sqlClientType === 'mysql2' || sqlClientType === 'mysql') {
+ tableNameLengthLimit = 64;
+ } else if (sqlClientType === 'pg') {
+ tableNameLengthLimit = 63;
+ } else if (sqlClientType === 'mssql') {
+ tableNameLengthLimit = 128;
+ }
+
+ if (param.table.table_name.length > tableNameLengthLimit) {
+ NcError.badRequest(`Table name exceeds ${tableNameLengthLimit} characters`);
+ }
+
+ const mxColumnLength = Column.getMaxColumnNameLength(sqlClientType);
+
+ for (const column of param.table.columns) {
+ if (column.column_name.length > mxColumnLength) {
+ NcError.badRequest(
+ `Column name ${column.column_name} exceeds ${mxColumnLength} characters`
+ );
+ }
+ }
+
+ param.table.columns = param.table.columns?.map((c) => ({
+ ...getColumnPropsFromUIDT(c as any, base),
+ cn: c.column_name,
+ }));
+ await sqlMgr.sqlOpPlus(base, 'tableCreate', {
+ ...param.table,
+ tn: param.table.table_name,
+ });
+
+ const columns: Array<
+ Omit & {
+ cn: string;
+ system?: boolean;
+ }
+ > = (await sqlClient.columnList({ tn: param.table.table_name }))?.data?.list;
+
+ const tables = await Model.list({
+ project_id: project.id,
+ base_id: base.id,
+ });
+
+ await Audit.insert({
+ project_id: project.id,
+ base_id: base.id,
+ op_type: AuditOperationTypes.TABLE,
+ op_sub_type: AuditOperationSubTypes.CREATED,
+ user: param.user?.email,
+ description: `created table ${param.table.table_name} with alias ${param.table.title} `,
+ ip: param.req?.clientIp,
+ }).then(() => {});
+
+ mapDefaultDisplayValue(param.table.columns);
+
+ T.emit('evt', { evt_type: 'table:created' });
+
+ // todo: type correction
+ const result = await Model.insert(project.id, base.id, {
+ ...param.table,
+ columns: columns.map((c, i) => {
+ const colMetaFromReq = param.table?.columns?.find(
+ (c1) => c.cn === c1.column_name
+ );
+ return {
+ ...colMetaFromReq,
+ uidt: colMetaFromReq?.uidt || c.uidt || getColumnUiType(base, c),
+ ...c,
+ dtxp: [UITypes.MultiSelect, UITypes.SingleSelect].includes(
+ colMetaFromReq.uidt as any
+ )
+ ? colMetaFromReq.dtxp
+ : c.dtxp,
+ title: colMetaFromReq?.title || getColumnNameAlias(c.cn, base),
+ column_name: c.cn,
+ order: i + 1,
+ } as NormalColumnRequestType;
+ }),
+ order: +(tables?.pop()?.order ?? 0) + 1,
+ } as any);
+
+ return result;
+}
diff --git a/packages/nocodb/src/lib/meta/api/userApi/helpers.ts b/packages/nocodb/src/lib/services/userService/helpers.ts
similarity index 83%
rename from packages/nocodb/src/lib/meta/api/userApi/helpers.ts
rename to packages/nocodb/src/lib/services/userService/helpers.ts
index e7686b2a81..387b51170e 100644
--- a/packages/nocodb/src/lib/meta/api/userApi/helpers.ts
+++ b/packages/nocodb/src/lib/services/userService/helpers.ts
@@ -1,7 +1,7 @@
import * as jwt from 'jsonwebtoken';
import crypto from 'crypto';
-import User from '../../../models/User';
-import { NcConfig } from '../../../../interface/config';
+import User from '../../models/User';
+import { NcConfig } from '../../../interface/config';
export function genJwt(user: User, config: NcConfig) {
return jwt.sign(
diff --git a/packages/nocodb/src/lib/services/userService/index.ts b/packages/nocodb/src/lib/services/userService/index.ts
new file mode 100644
index 0000000000..d9e42bd651
--- /dev/null
+++ b/packages/nocodb/src/lib/services/userService/index.ts
@@ -0,0 +1,300 @@
+import {
+ PasswordChangeReqType,
+ PasswordForgotReqType,
+ PasswordResetReqType,
+ UserType,
+ validatePassword,
+} from 'nocodb-sdk';
+import { OrgUserRoles } from 'nocodb-sdk';
+import { T } from 'nc-help';
+
+import * as ejs from 'ejs';
+
+import bcrypt from 'bcryptjs';
+import { promisify } from 'util';
+import { NC_APP_SETTINGS } from '../../constants';
+import { validatePayload } from '../../meta/api/helpers';
+import { NcError } from '../../meta/helpers/catchError';
+import NcPluginMgrv2 from '../../meta/helpers/NcPluginMgrv2';
+import { Audit, Store, User } from '../../models';
+import Noco from '../../Noco';
+import { MetaTable } from '../../utils/globals';
+import { randomTokenString } from './helpers';
+
+const { v4: uuidv4 } = require('uuid');
+
+export async function registerNewUserIfAllowed({
+ firstname,
+ lastname,
+ email,
+ salt,
+ password,
+ email_verification_token,
+}: {
+ firstname;
+ lastname;
+ email: string;
+ salt: any;
+ password;
+ email_verification_token;
+}) {
+ let roles: string = OrgUserRoles.CREATOR;
+
+ if (await User.isFirst()) {
+ roles = `${OrgUserRoles.CREATOR},${OrgUserRoles.SUPER_ADMIN}`;
+ // todo: update in nc_store
+ // roles = 'owner,creator,editor'
+ T.emit('evt', {
+ evt_type: 'project:invite',
+ count: 1,
+ });
+ } else {
+ let settings: { invite_only_signup?: boolean } = {};
+ try {
+ settings = JSON.parse((await Store.get(NC_APP_SETTINGS))?.value);
+ } catch {}
+
+ if (settings?.invite_only_signup) {
+ NcError.badRequest('Not allowed to signup, contact super admin.');
+ } else {
+ roles = OrgUserRoles.VIEWER;
+ }
+ }
+
+ const token_version = randomTokenString();
+
+ return await User.insert({
+ firstname,
+ lastname,
+ email,
+ salt,
+ password,
+ email_verification_token,
+ roles,
+ token_version,
+ });
+}
+
+export async function passwordChange(param: {
+ body: PasswordChangeReqType;
+ user: UserType;
+ req: any;
+}): Promise {
+ validatePayload(
+ 'swagger.json#/components/schemas/PasswordChangeReq',
+ param.body
+ );
+
+ const { currentPassword, newPassword } = param.body;
+
+ if (!currentPassword || !newPassword) {
+ return NcError.badRequest('Missing new/old password');
+ }
+
+ // validate password and throw error if password is satisfying the conditions
+ const { valid, error } = validatePassword(newPassword);
+
+ if (!valid) {
+ NcError.badRequest(`Password : ${error}`);
+ }
+
+ const user = await User.getByEmail(param.user.email);
+
+ const hashedPassword = await promisify(bcrypt.hash)(
+ currentPassword,
+ user.salt
+ );
+
+ if (hashedPassword !== user.password) {
+ return NcError.badRequest('Current password is wrong');
+ }
+
+ const salt = await promisify(bcrypt.genSalt)(10);
+ const password = await promisify(bcrypt.hash)(newPassword, salt);
+
+ await User.update(user.id, {
+ salt,
+ password,
+ email: user.email,
+ token_version: null,
+ });
+
+ await Audit.insert({
+ op_type: 'AUTHENTICATION',
+ op_sub_type: 'PASSWORD_CHANGE',
+ user: user.email,
+ description: `changed password `,
+ ip: param.req?.clientIp,
+ });
+
+ return true;
+}
+
+export async function passwordForgot(param: {
+ body: PasswordForgotReqType;
+ siteUrl: string;
+ req: any;
+}): Promise {
+ validatePayload(
+ 'swagger.json#/components/schemas/ForgotPasswordReq',
+ param.body
+ );
+
+ const _email = param.body.email;
+
+ if (!_email) {
+ NcError.badRequest('Please enter your email address.');
+ }
+
+ const email = _email.toLowerCase();
+ const user = await User.getByEmail(email);
+
+ if (user) {
+ const token = uuidv4();
+ await User.update(user.id, {
+ email: user.email,
+ reset_password_token: token,
+ reset_password_expires: new Date(Date.now() + 60 * 60 * 1000),
+ token_version: null,
+ });
+ try {
+ const template = (await import('./ui/emailTemplates/forgotPassword'))
+ .default;
+ await NcPluginMgrv2.emailAdapter().then((adapter) =>
+ adapter.mailSend({
+ to: user.email,
+ subject: 'Password Reset Link',
+ text: `Visit following link to update your password : ${param.siteUrl}/auth/password/reset/${token}.`,
+ html: ejs.render(template, {
+ resetLink: param.siteUrl + `/auth/password/reset/${token}`,
+ }),
+ })
+ );
+ } catch (e) {
+ console.log(e);
+ return NcError.badRequest(
+ 'Email Plugin is not found. Please contact administrators to configure it in App Store first.'
+ );
+ }
+
+ await Audit.insert({
+ op_type: 'AUTHENTICATION',
+ op_sub_type: 'PASSWORD_FORGOT',
+ user: user.email,
+ description: `requested for password reset `,
+ ip: param.req?.clientIp,
+ });
+ } else {
+ return NcError.badRequest('Your email has not been registered.');
+ }
+
+ return true;
+}
+
+export async function tokenValidate(param: { token: string }): Promise {
+ const token = param.token;
+
+ const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, {
+ reset_password_token: token,
+ });
+
+ if (!user || !user.email) {
+ NcError.badRequest('Invalid reset url');
+ }
+ if (new Date(user.reset_password_expires) < new Date()) {
+ NcError.badRequest('Password reset url expired');
+ }
+
+ return true;
+}
+
+export async function passwordReset(param: {
+ body: PasswordResetReqType;
+ token: string;
+ // todo: exclude
+ req: any;
+}): Promise {
+ validatePayload(
+ 'swagger.json#/components/schemas/PasswordResetReq',
+ param.body
+ );
+
+ const { token, body, req } = param;
+
+ const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, {
+ reset_password_token: token,
+ });
+
+ if (!user) {
+ NcError.badRequest('Invalid reset url');
+ }
+ if (user.reset_password_expires < new Date()) {
+ NcError.badRequest('Password reset url expired');
+ }
+ if (user.provider && user.provider !== 'local') {
+ NcError.badRequest('Email registered via social account');
+ }
+
+ // validate password and throw error if password is satisfying the conditions
+ const { valid, error } = validatePassword(body.password);
+ if (!valid) {
+ NcError.badRequest(`Password : ${error}`);
+ }
+
+ const salt = await promisify(bcrypt.genSalt)(10);
+ const password = await promisify(bcrypt.hash)(body.password, salt);
+
+ await User.update(user.id, {
+ salt,
+ password,
+ email: user.email,
+ reset_password_expires: null,
+ reset_password_token: '',
+ token_version: null,
+ });
+
+ await Audit.insert({
+ op_type: 'AUTHENTICATION',
+ op_sub_type: 'PASSWORD_RESET',
+ user: user.email,
+ description: `did reset password `,
+ ip: req.clientIp,
+ });
+
+ return true;
+}
+
+export async function emailVerification(param: {
+ token: string;
+ // todo: exclude
+ req: any;
+}): Promise {
+ const { token, req } = param;
+
+ const user = await Noco.ncMeta.metaGet(null, null, MetaTable.USERS, {
+ email_verification_token: token,
+ });
+
+ if (!user) {
+ NcError.badRequest('Invalid verification url');
+ }
+
+ await User.update(user.id, {
+ email: user.email,
+ email_verification_token: '',
+ email_verified: true,
+ });
+
+ await Audit.insert({
+ op_type: 'AUTHENTICATION',
+ op_sub_type: 'EMAIL_VERIFICATION',
+ user: user.email,
+ description: `verified email `,
+ ip: req.clientIp,
+ });
+
+ return true;
+}
+
+export * from './helpers';
+export { default as initAdminFromEnv } from './initAdminFromEnv';
diff --git a/packages/nocodb/src/lib/meta/api/userApi/initAdminFromEnv.ts b/packages/nocodb/src/lib/services/userService/initAdminFromEnv.ts
similarity index 96%
rename from packages/nocodb/src/lib/meta/api/userApi/initAdminFromEnv.ts
rename to packages/nocodb/src/lib/services/userService/initAdminFromEnv.ts
index 8f9bfdce74..1a15b94649 100644
--- a/packages/nocodb/src/lib/meta/api/userApi/initAdminFromEnv.ts
+++ b/packages/nocodb/src/lib/services/userService/initAdminFromEnv.ts
@@ -1,15 +1,14 @@
-import User from '../../../models/User';
import { v4 as uuidv4 } from 'uuid';
import { promisify } from 'util';
import bcrypt from 'bcryptjs';
-import Noco from '../../../Noco';
-import { CacheScope, MetaTable } from '../../../utils/globals';
-import ProjectUser from '../../../models/ProjectUser';
import { validatePassword } from 'nocodb-sdk';
import boxen from 'boxen';
-import NocoCache from '../../../cache/NocoCache';
-import { Tele } from 'nc-help';
+import { T } from 'nc-help';
+import NocoCache from '../../cache/NocoCache';
+import { ProjectUser, User } from '../../models';
+import Noco from '../../Noco';
+import { CacheScope, MetaTable } from '../../utils/globals';
const { isEmail } = require('validator');
const rolesLevel = { owner: 0, creator: 1, editor: 2, commenter: 3, viewer: 4 };
@@ -68,7 +67,7 @@ export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) {
// if super admin not present
if (await User.isFirst(ncMeta)) {
// roles = 'owner,creator,editor'
- Tele.emit('evt', {
+ T.emit('evt', {
evt_type: 'project:invite',
count: 1,
});
@@ -126,7 +125,7 @@ export default async function initAdminFromEnv(_ncMeta = Noco.ncMeta) {
ncMeta
);
} else {
- Tele.emit('evt', {
+ T.emit('evt', {
evt_type: 'project:invite',
count: 1,
});
diff --git a/packages/nocodb/src/lib/services/userService/ui/auth/emailVerify.ts b/packages/nocodb/src/lib/services/userService/ui/auth/emailVerify.ts
new file mode 100644
index 0000000000..f7412b12ba
--- /dev/null
+++ b/packages/nocodb/src/lib/services/userService/ui/auth/emailVerify.ts
@@ -0,0 +1,70 @@
+export default `
+
+
+ NocoDB - Verify Email
+
+
+
+
+
+
+
+
+
+
+