Browse Source

Merge pull request #5262 from nocodb/refactor/ctl-svc-layer

refactor: ctl & svc layer
pull/5252/head
Pranav C 2 years ago committed by GitHub
parent
commit
7da8114ce5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      packages/nocodb-sdk/src/lib/Api.ts
  2. 4
      packages/nocodb/src/lib/controllers/dbData/helpers.ts
  3. 12
      packages/nocodb/src/lib/controllers/dbData/oldData.ctl.ts
  4. 2
      packages/nocodb/src/lib/controllers/publicControllers/publicDataExport.ctl.ts
  5. 0
      packages/nocodb/src/lib/controllers/sync/import.ctl.ts
  6. 71
      packages/nocodb/src/lib/controllers/sync/index.ts
  7. 68
      packages/nocodb/src/lib/controllers/sync/sync.ctl.ts
  8. 446
      packages/nocodb/src/lib/controllers/user/index.ts
  9. 250
      packages/nocodb/src/lib/controllers/user/user.ctl.ts
  10. 16
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts
  11. 2
      packages/nocodb/src/lib/db/sql-mgr/v2/SqlMgrv2Trans.ts
  12. 2
      packages/nocodb/src/lib/db/sql-migrator/lib/KnexMigratorv2.ts
  13. 2
      packages/nocodb/src/lib/meta/api/helpers/populateMeta.ts
  14. 3
      packages/nocodb/src/lib/meta/api/index.ts
  15. 4
      packages/nocodb/src/lib/meta/helpers/extractProps.ts
  16. 4
      packages/nocodb/src/lib/meta/helpers/getColumnPropsFromUIDT.ts
  17. 5
      packages/nocodb/src/lib/models/Base.ts
  18. 2
      packages/nocodb/src/lib/models/Hook.ts
  19. 3
      packages/nocodb/src/lib/models/KanbanView.ts
  20. 2
      packages/nocodb/src/lib/models/KanbanViewColumn.ts
  21. 2
      packages/nocodb/src/lib/services/base.svc.ts
  22. 18
      packages/nocodb/src/lib/services/column.svc.ts
  23. 2
      packages/nocodb/src/lib/services/dbData/bulkData.ts
  24. 14
      packages/nocodb/src/lib/services/dbData/dataAliasNested.svc.ts
  25. 4
      packages/nocodb/src/lib/services/dbData/helpers.ts
  26. 42
      packages/nocodb/src/lib/services/dbData/index.ts
  27. 0
      packages/nocodb/src/lib/services/ee/orgToken.svc.ts
  28. 4
      packages/nocodb/src/lib/services/index.ts
  29. 6
      packages/nocodb/src/lib/services/public/index.ts
  30. 12
      packages/nocodb/src/lib/services/public/publicData.svc.ts
  31. 2
      packages/nocodb/src/lib/services/public/publicDataExport.svc.ts
  32. 0
      packages/nocodb/src/lib/services/public/publicMeta.svc.ts
  33. 7
      packages/nocodb/src/lib/services/sync/index.ts
  34. 55
      packages/nocodb/src/lib/services/table.svc.ts
  35. 10
      packages/nocodb/src/lib/services/user/helpers.ts
  36. 164
      packages/nocodb/src/lib/services/user/index.ts
  37. 105
      packages/nocodb/src/lib/utils/common/NcConnectionMgrv2.ts
  38. 2
      packages/nocodb/src/lib/version-upgrader/ncAttachmentUpgrader.ts
  39. 2
      packages/nocodb/src/lib/version-upgrader/ncAttachmentUpgrader_0104002.ts
  40. 44
      packages/nocodb/src/schema/swagger.json
  41. 2
      packages/nocodb/tests/unit/factory/row.ts
  42. 8
      packages/nocodb/tests/unit/model/tests/baseModelSql.test.ts
  43. 2
      tests/playwright/tests/filters.spec.ts

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

@ -1960,6 +1960,14 @@ export interface SignUpReqType {
* @example password123456789 * @example password123456789
*/ */
password: string; password: string;
/** Model for StringOrNull */
firstname?: StringOrNullType;
/** Model for StringOrNull */
lastname?: StringOrNullType;
/** Sign Up Token. Used for invitation. */
token?: StringOrNullType;
/** Ignore Subscription */
ignore_subscribe?: BoolType;
} }
/** /**

4
packages/nocodb/src/lib/controllers/dbData/helpers.ts

@ -57,7 +57,7 @@ export async function extractXlsxData(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: view.model.id, id: view.model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const { offset, dbRows, elapsed } = await dataService.getDbRows({ const { offset, dbRows, elapsed } = await dataService.getDbRows({
@ -92,7 +92,7 @@ export async function extractCsvData(view: View, req: Request) {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: view.model.id, id: view.model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const { offset, dbRows, elapsed } = await dataService.getDbRows({ const { offset, dbRows, elapsed } = await dataService.getDbRows({

12
packages/nocodb/src/lib/controllers/dbData/oldData.ctl.ts

@ -17,7 +17,7 @@ export async function dataList(req: Request, res: Response) {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const requestObj = await getAst({ const requestObj = await getAst({
@ -50,7 +50,7 @@ export async function dataCount(req: Request, res: Response) {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const listArgs: any = { ...req.query }; const listArgs: any = { ...req.query };
@ -73,7 +73,7 @@ async function dataInsert(req: Request, res: Response) {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
res.json(await baseModel.insert(req.body, null, req)); res.json(await baseModel.insert(req.body, null, req));
@ -86,7 +86,7 @@ async function dataUpdate(req: Request, res: Response) {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view.id, viewId: view.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
res.json(await baseModel.updateByPk(req.params.rowId, req.body, null, req)); res.json(await baseModel.updateByPk(req.params.rowId, req.body, null, req));
@ -98,7 +98,7 @@ async function dataDelete(req: Request, res: Response) {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view.id, viewId: view.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
res.json(await baseModel.delByPk(req.params.rowId, null, req)); res.json(await baseModel.delByPk(req.params.rowId, null, req));
@ -128,7 +128,7 @@ async function dataRead(req: Request, res: Response) {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
res.json( res.json(

2
packages/nocodb/src/lib/controllers/publicControllers/publicDataExport.ctl.ts

@ -138,7 +138,7 @@ async function getDbRows(model, view: View, req: Request) {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const requestObj = await getAst({ const requestObj = await getAst({

0
packages/nocodb/src/lib/controllers/sync/importApis.ts → packages/nocodb/src/lib/controllers/sync/import.ctl.ts

71
packages/nocodb/src/lib/controllers/sync/index.ts

@ -1,69 +1,4 @@
import { Request, Response, Router } from 'express'; import importController from './import.ctl';
import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw'; import syncSourceController from './sync.ctl';
import { syncService } from '../../services';
export async function syncSourceList(req: Request, res: Response) { export { importController, syncSourceController };
// todo: pagination
res.json(
syncService.syncSourceList({
projectId: req.params.projectId,
})
);
}
export async function syncCreate(req: Request, res: Response) {
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<any>) {
res.json(
await syncService.syncDelete({
syncId: req.params.syncId,
})
);
}
export async function syncUpdate(req: Request, res: Response) {
res.json(
await syncService.syncUpdate({
syncId: req.params.syncId,
syncPayload: req.body,
})
);
}
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/meta/projects/:projectId/syncs',
ncMetaAclMw(syncSourceList, 'syncSourceList')
);
router.post(
'/api/v1/db/meta/projects/:projectId/syncs',
ncMetaAclMw(syncCreate, 'syncSourceCreate')
);
router.get(
'/api/v1/db/meta/projects/:projectId/syncs/:baseId',
ncMetaAclMw(syncSourceList, 'syncSourceList')
);
router.post(
'/api/v1/db/meta/projects/:projectId/syncs/:baseId',
ncMetaAclMw(syncCreate, 'syncSourceCreate')
);
router.delete(
'/api/v1/db/meta/syncs/:syncId',
ncMetaAclMw(syncDelete, 'syncSourceDelete')
);
router.patch(
'/api/v1/db/meta/syncs/:syncId',
ncMetaAclMw(syncUpdate, 'syncSourceUpdate')
);
export default router;

68
packages/nocodb/src/lib/controllers/sync/sync.ctl.ts

@ -0,0 +1,68 @@
import { Request, Response, Router } from 'express';
import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw';
import { syncService } from '../../services';
export async function syncSourceList(req: Request, res: Response) {
res.json(
await syncService.syncSourceList({
projectId: req.params.projectId,
})
);
}
export async function syncCreate(req: Request, res: Response) {
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<any>) {
res.json(
await syncService.syncDelete({
syncId: req.params.syncId,
})
);
}
export async function syncUpdate(req: Request, res: Response) {
res.json(
await syncService.syncUpdate({
syncId: req.params.syncId,
syncPayload: req.body,
})
);
}
const router = Router({ mergeParams: true });
router.get(
'/api/v1/db/meta/projects/:projectId/syncs',
ncMetaAclMw(syncSourceList, 'syncSourceList')
);
router.post(
'/api/v1/db/meta/projects/:projectId/syncs',
ncMetaAclMw(syncCreate, 'syncSourceCreate')
);
router.get(
'/api/v1/db/meta/projects/:projectId/syncs/:baseId',
ncMetaAclMw(syncSourceList, 'syncSourceList')
);
router.post(
'/api/v1/db/meta/projects/:projectId/syncs/:baseId',
ncMetaAclMw(syncCreate, 'syncSourceCreate')
);
router.delete(
'/api/v1/db/meta/syncs/:syncId',
ncMetaAclMw(syncDelete, 'syncSourceDelete')
);
router.patch(
'/api/v1/db/meta/syncs/:syncId',
ncMetaAclMw(syncUpdate, 'syncSourceUpdate')
);
export default router;

446
packages/nocodb/src/lib/controllers/user/index.ts

@ -1,445 +1,3 @@
import { Request, Response } from 'express'; import userController from './user.ctl';
import { TableType, validatePassword } from 'nocodb-sdk';
import { T } from 'nc-help';
const { isEmail } = require('validator'); export { userController };
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<TableType>) {
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<any> =>
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<any> =>
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<any> {
res.json(req?.session?.passport?.user ?? {});
}
async function passwordChange(req: Request<any, any>, res): Promise<any> {
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<any, any>, res): Promise<any> {
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<any> {
await userService.tokenValidate({
token: req.params.tokenId,
});
res.json(true);
}
async function passwordReset(req, res): Promise<any> {
await userService.passwordReset({
token: req.params.tokenId,
body: req.body,
req,
});
res.json({ msg: 'Password reset successful' });
}
async function emailVerification(req, res): Promise<any> {
await userService.emailVerification({
token: req.params.tokenId,
req,
});
res.json({ msg: 'Email verified successfully' });
}
async function refreshToken(req, res): Promise<any> {
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<any> {
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 };

250
packages/nocodb/src/lib/controllers/user/userApis.ts → packages/nocodb/src/lib/controllers/user/user.ctl.ts

@ -1,139 +1,34 @@
import { Request, Response } from 'express'; import { Request } from 'express';
import { TableType, validatePassword } from 'nocodb-sdk';
import { T } from 'nc-help';
const { isEmail } = require('validator');
import * as ejs from 'ejs'; import * as ejs from 'ejs';
import bcrypt from 'bcryptjs';
import { promisify } from 'util'; import { promisify } from 'util';
const { v4: uuidv4 } = require('uuid');
import passport from 'passport'; import passport from 'passport';
import { getAjvValidatorMw } from '../../meta/api/helpers';
import catchError, { NcError } from '../../meta/helpers/catchError'; import catchError, { NcError } from '../../meta/helpers/catchError';
import extractProjectIdAndAuthenticate from '../../meta/helpers/extractProjectIdAndAuthenticate'; import extractProjectIdAndAuthenticate from '../../meta/helpers/extractProjectIdAndAuthenticate';
import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw'; import ncMetaAclMw from '../../meta/helpers/ncMetaAclMw';
import NcPluginMgrv2 from '../../meta/helpers/NcPluginMgrv2';
import { Audit, User } from '../../models'; import { Audit, User } from '../../models';
import Noco from '../../Noco'; import Noco from '../../Noco';
import { userService } from '../../services'; import { userService } from '../../services';
import { setTokenCookie } from '../../services/user/helpers';
export async function signup(req: Request<any, any>, res): Promise<any> {
res.json(
await userService.signup({
body: req.body,
req,
res,
})
);
}
export async function signup(req: Request, res: Response<TableType>) { export async function refreshToken(req: Request<any, any>, res): Promise<any> {
const { res.json(
email: _email, await userService.refreshToken({
firstname, body: req.body,
lastname, req,
token, res,
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({ async function successfulSignIn({
@ -156,6 +51,7 @@ async function successfulSignIn({
} }
await promisify((req as any).login.bind(req))(user); await promisify((req as any).login.bind(req))(user);
const refreshToken = userService.randomTokenString(); const refreshToken = userService.randomTokenString();
if (!user.token_version) { if (!user.token_version) {
@ -221,15 +117,6 @@ async function googleSignin(req, res, next) {
)(req, res, next); )(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<any> { async function me(req, res): Promise<any> {
res.json(req?.session?.passport?.user ?? {}); res.json(req?.session?.passport?.user ?? {});
} }
@ -284,35 +171,6 @@ async function emailVerification(req, res): Promise<any> {
res.json({ msg: 'Email verified successfully' }); res.json({ msg: 'Email verified successfully' });
} }
async function refreshToken(req, res): Promise<any> {
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<any> { async function renderPasswordReset(req, res): Promise<any> {
try { try {
res.send( res.send(
@ -329,32 +187,15 @@ async function renderPasswordReset(req, res): Promise<any> {
const mapRoutes = (router) => { const mapRoutes = (router) => {
// todo: old api - /auth/signup?tool=1 // todo: old api - /auth/signup?tool=1
router.post( router.post('/auth/user/signup', catchError(signup));
'/auth/user/signup', router.post('/auth/user/signin', catchError(signin));
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.get('/auth/user/me', extractProjectIdAndAuthenticate, catchError(me));
router.post( router.post('/auth/password/forgot', catchError(passwordForgot));
'/auth/password/forgot',
getAjvValidatorMw('swagger.json#/components/schemas/ForgotPasswordReq'),
catchError(passwordForgot)
);
router.post('/auth/token/validate/:tokenId', catchError(tokenValidate)); router.post('/auth/token/validate/:tokenId', catchError(tokenValidate));
router.post( router.post('/auth/password/reset/:tokenId', catchError(passwordReset));
'/auth/password/reset/:tokenId',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordResetReq'),
catchError(passwordReset)
);
router.post('/auth/email/validate/:tokenId', catchError(emailVerification)); router.post('/auth/email/validate/:tokenId', catchError(emailVerification));
router.post( router.post(
'/user/password/change', '/user/password/change',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordChangeReq'),
ncMetaAclMw(passwordChange, 'passwordChange') ncMetaAclMw(passwordChange, 'passwordChange')
); );
router.post('/auth/token/refresh', catchError(refreshToken)); router.post('/auth/token/refresh', catchError(refreshToken));
@ -372,33 +213,20 @@ const mapRoutes = (router) => {
); );
// deprecated APIs // deprecated APIs
router.post( router.post('/api/v1/db/auth/user/signup', catchError(signup));
'/api/v1/db/auth/user/signup', router.post('/api/v1/db/auth/user/signin', catchError(signin));
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( router.get(
'/api/v1/db/auth/user/me', '/api/v1/db/auth/user/me',
extractProjectIdAndAuthenticate, extractProjectIdAndAuthenticate,
catchError(me) catchError(me)
); );
router.post( router.post('/api/v1/db/auth/password/forgot', catchError(passwordForgot));
'/api/v1/db/auth/password/forgot',
getAjvValidatorMw('swagger.json#/components/schemas/ForgotPasswordReq'),
catchError(passwordForgot)
);
router.post( router.post(
'/api/v1/db/auth/token/validate/:tokenId', '/api/v1/db/auth/token/validate/:tokenId',
catchError(tokenValidate) catchError(tokenValidate)
); );
router.post( router.post(
'/api/v1/db/auth/password/reset/:tokenId', '/api/v1/db/auth/password/reset/:tokenId',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordResetReq'),
catchError(passwordReset) catchError(passwordReset)
); );
router.post( router.post(
@ -407,7 +235,6 @@ const mapRoutes = (router) => {
); );
router.post( router.post(
'/api/v1/db/auth/password/change', '/api/v1/db/auth/password/change',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordChangeReq'),
ncMetaAclMw(passwordChange, 'passwordChange') ncMetaAclMw(passwordChange, 'passwordChange')
); );
router.post('/api/v1/db/auth/token/refresh', catchError(refreshToken)); router.post('/api/v1/db/auth/token/refresh', catchError(refreshToken));
@ -417,26 +244,14 @@ const mapRoutes = (router) => {
); );
// new API // new API
router.post( router.post('/api/v1/auth/user/signup', catchError(signup));
'/api/v1/auth/user/signup', router.post('/api/v1/auth/user/signin', catchError(signin));
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( router.get(
'/api/v1/auth/user/me', '/api/v1/auth/user/me',
extractProjectIdAndAuthenticate, extractProjectIdAndAuthenticate,
catchError(me) catchError(me)
); );
router.post( router.post('/api/v1/auth/password/forgot', catchError(passwordForgot));
'/api/v1/auth/password/forgot',
getAjvValidatorMw('swagger.json#/components/schemas/ForgotPasswordReq'),
catchError(passwordForgot)
);
router.post( router.post(
'/api/v1/auth/token/validate/:tokenId', '/api/v1/auth/token/validate/:tokenId',
catchError(tokenValidate) catchError(tokenValidate)
@ -451,11 +266,10 @@ const mapRoutes = (router) => {
); );
router.post( router.post(
'/api/v1/auth/password/change', '/api/v1/auth/password/change',
getAjvValidatorMw('swagger.json#/components/schemas/PasswordChangeReq'),
ncMetaAclMw(passwordChange, 'passwordChange') ncMetaAclMw(passwordChange, 'passwordChange')
); );
router.post('/api/v1/auth/token/refresh', catchError(refreshToken)); router.post('/api/v1/auth/token/refresh', catchError(refreshToken));
// respond with password reset page // respond with password reset page
router.get('/auth/password/reset/:tokenId', catchError(renderPasswordReset)); router.get('/auth/password/reset/:tokenId', catchError(renderPasswordReset));
}; };
export { mapRoutes as userController }; export default mapRoutes;

16
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts

@ -576,7 +576,7 @@ const parseConditionV2 = async (
} }
} }
break; break;
case 'gt': case 'gt': {
const gt_op = customWhereClause ? '<' : '>'; const gt_op = customWhereClause ? '<' : '>';
qb = qb.where(field, gt_op, val); qb = qb.where(field, gt_op, val);
if (column.uidt === UITypes.Rating) { if (column.uidt === UITypes.Rating) {
@ -586,8 +586,9 @@ const parseConditionV2 = async (
} }
} }
break; break;
}
case 'ge': case 'ge':
case 'gte': case 'gte': {
const ge_op = customWhereClause ? '<=' : '>='; const ge_op = customWhereClause ? '<=' : '>=';
qb = qb.where(field, ge_op, val); qb = qb.where(field, ge_op, val);
if (column.uidt === UITypes.Rating) { if (column.uidt === UITypes.Rating) {
@ -597,7 +598,8 @@ const parseConditionV2 = async (
} }
} }
break; break;
case 'lt': }
case 'lt': {
const lt_op = customWhereClause ? '>' : '<'; const lt_op = customWhereClause ? '>' : '<';
qb = qb.where(field, lt_op, val); qb = qb.where(field, lt_op, val);
if (column.uidt === UITypes.Rating) { if (column.uidt === UITypes.Rating) {
@ -607,8 +609,10 @@ const parseConditionV2 = async (
} }
} }
break; break;
}
case 'le': case 'le':
case 'lte': case 'lte': {
const le_op = customWhereClause ? '>=' : '<='; const le_op = customWhereClause ? '>=' : '<=';
qb = qb.where(field, le_op, val); qb = qb.where(field, le_op, val);
if (column.uidt === UITypes.Rating) { if (column.uidt === UITypes.Rating) {
@ -618,6 +622,7 @@ const parseConditionV2 = async (
} }
} }
break; break;
}
case 'in': case 'in':
qb = qb.whereIn( qb = qb.whereIn(
field, field,
@ -720,7 +725,7 @@ const parseConditionV2 = async (
case 'nbtw': case 'nbtw':
qb = qb.whereNotBetween(field, val.split(',')); qb = qb.whereNotBetween(field, val.split(','));
break; break;
case 'isWithin': case 'isWithin': {
let now = dayjs(new Date()).format(dateFormat).toString(); let now = dayjs(new Date()).format(dateFormat).toString();
now = column.uidt === UITypes.Date ? now.substring(0, 10) : now; now = column.uidt === UITypes.Date ? now.substring(0, 10) : now;
switch (filter.comparison_sub_op) { switch (filter.comparison_sub_op) {
@ -737,6 +742,7 @@ const parseConditionV2 = async (
qb = qb.whereBetween(field, [now, val]); qb = qb.whereBetween(field, [now, val]);
break; break;
} }
}
} }
}; };
} }

2
packages/nocodb/src/lib/db/sql-mgr/v2/SqlMgrv2Trans.ts

@ -34,7 +34,7 @@ export default class SqlMgrv2Trans extends SqlMgrv2 {
} }
public async startTransaction(base: Base) { public async startTransaction(base: Base) {
const knex: XKnex = NcConnectionMgrv2.get(base); const knex: XKnex = await NcConnectionMgrv2.get(base);
this.trx = await knex.transaction(); this.trx = await knex.transaction();
} }

2
packages/nocodb/src/lib/db/sql-migrator/lib/KnexMigratorv2.ts

@ -384,7 +384,7 @@ export default class KnexMigratorv2 {
async _initDbWithSql(base: Base) { async _initDbWithSql(base: Base) {
const sqlClient = await this.getSqlClient(base); const sqlClient = await this.getSqlClient(base);
const connectionConfig = base.getConnectionConfig(); const connectionConfig = await base.getConnectionConfig();
if (connectionConfig.client === 'oracledb') { if (connectionConfig.client === 'oracledb') {
this.emit( this.emit(
`${connectionConfig.client}: Creating DB if not exists ${connectionConfig.connection.user}` `${connectionConfig.client}: Creating DB if not exists ${connectionConfig.connection.user}`

2
packages/nocodb/src/lib/meta/api/helpers/populateMeta.ts

@ -22,7 +22,7 @@ export async function populateMeta(base: Base, project: Project): Promise<any> {
tablesCount: 0, tablesCount: 0,
relationsCount: 0, relationsCount: 0,
viewsCount: 0, viewsCount: 0,
client: base?.getConnectionConfig()?.client, client: (await base?.getConnectionConfig())?.client,
timeTaken: 0, timeTaken: 0,
}; };

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

@ -52,8 +52,7 @@ import passport from 'passport';
import crypto from 'crypto'; import crypto from 'crypto';
import swaggerController from '../../controllers/apiDocs'; import swaggerController from '../../controllers/apiDocs';
import importController from '../../controllers/sync/importApis'; import { importController, syncSourceController } from '../../controllers/sync';
import syncSourceController from '../../controllers/sync';
import mapViewController from '../../controllers/views/mapView.ctl'; import mapViewController from '../../controllers/views/mapView.ctl';
const clients: { [id: string]: Socket } = {}; const clients: { [id: string]: Socket } = {};

4
packages/nocodb/src/lib/meta/helpers/extractProps.ts

@ -1,6 +1,6 @@
import DOMPurify from 'isomorphic-dompurify'; import DOMPurify from 'isomorphic-dompurify';
export function extractProps<T extends object>( export function extractProps<T extends Record<string, any>>(
body: T, body: T,
props: string[] props: string[]
): Partial<T> { ): Partial<T> {
@ -11,7 +11,7 @@ export function extractProps<T extends object>(
}, {}); }, {});
} }
export function extractPropsAndSanitize<T extends object>( export function extractPropsAndSanitize<T extends Record<string, any>>(
body: T, body: T,
props: string[] props: string[]
): Partial<T> { ): Partial<T> {

4
packages/nocodb/src/lib/meta/helpers/getColumnPropsFromUIDT.ts

@ -7,11 +7,11 @@ import {
import Base from '../../models/Base'; import Base from '../../models/Base';
import Column from '../../models/Column'; import Column from '../../models/Column';
export default function getColumnPropsFromUIDT( export default async function getColumnPropsFromUIDT(
column: ColumnReqType & { altered?: number }, column: ColumnReqType & { altered?: number },
base: Base base: Base
) { ) {
const sqlUi = SqlUiFactory.create(base.getConnectionConfig()); const sqlUi = SqlUiFactory.create(await base.getConnectionConfig());
const colProp = sqlUi.getDataTypeForUiType( const colProp = sqlUi.getDataTypeForUiType(
column as Column, column as Column,

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

@ -234,14 +234,15 @@ export default class Base implements BaseType {
} }
} }
public getConnectionConfig(): any { // NC_DATA_DB is not available in community version
// make it return Promise to avoid conflicts
public getConnectionConfig(): Promise<any> {
if (this.is_meta) { if (this.is_meta) {
const metaConfig = Noco.getConfig()?.meta?.db; const metaConfig = Noco.getConfig()?.meta?.db;
const config = { ...metaConfig }; const config = { ...metaConfig };
if (config.client === 'sqlite3') { if (config.client === 'sqlite3') {
config.connection = metaConfig; config.connection = metaConfig;
} }
return config; return config;
} }

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

@ -26,7 +26,7 @@ export default class Hook implements HookType {
url?: string; url?: string;
headers?: string; headers?: string;
condition?: BoolType; condition?: BoolType;
notification?: string | object; notification?: string | Record<string, any>;
retries?: number; retries?: number;
retry_interval?: number; retry_interval?: number;
timeout?: number; timeout?: number;

3
packages/nocodb/src/lib/models/KanbanView.ts

@ -1,5 +1,6 @@
import Noco from '../Noco'; import Noco from '../Noco';
import { BoolType, KanbanType, MetaType, UITypes } from 'nocodb-sdk'; import { UITypes } from 'nocodb-sdk';
import type { BoolType, KanbanType, MetaType } from 'nocodb-sdk';
import { CacheGetType, CacheScope, MetaTable } from '../utils/globals'; import { CacheGetType, CacheScope, MetaTable } from '../utils/globals';
import View from './View'; import View from './View';
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache';

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

@ -3,7 +3,7 @@ import { CacheGetType, CacheScope, MetaTable } from '../utils/globals';
import View from './View'; import View from './View';
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache';
import { extractProps } from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
import { BoolType, KanbanColumnType } from 'nocodb-sdk'; import type { BoolType, KanbanColumnType } from 'nocodb-sdk';
export default class KanbanViewColumn implements KanbanColumnType { export default class KanbanViewColumn implements KanbanColumnType {
id: string; id: string;

2
packages/nocodb/src/lib/services/base.svc.ts

@ -8,7 +8,7 @@ import { populateMeta, validatePayload } from '../meta/api/helpers';
export async function baseGetWithConfig(param: { baseId: any }) { export async function baseGetWithConfig(param: { baseId: any }) {
const base = await Base.get(param.baseId); const base = await Base.get(param.baseId);
base.config = base.getConnectionConfig(); base.config = await base.getConnectionConfig();
return base; return base;
} }

18
packages/nocodb/src/lib/services/column.svc.ts

@ -127,7 +127,7 @@ export async function columnUpdate(param: {
try { try {
// test the query to see if it is valid in db level // test the query to see if it is valid in db level
const dbDriver = NcConnectionMgrv2.get(base); const dbDriver = await NcConnectionMgrv2.get(base);
await formulaQueryBuilderv2(colBody.formula, null, dbDriver, table); await formulaQueryBuilderv2(colBody.formula, null, dbDriver, table);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@ -167,16 +167,16 @@ export async function columnUpdate(param: {
} else if ( } else if (
[UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt) [UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt)
) { ) {
colBody = getColumnPropsFromUIDT(colBody, base); colBody = await getColumnPropsFromUIDT(colBody, base);
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: table.id, id: table.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
if (colBody.colOptions?.options) { if (colBody.colOptions?.options) {
const supportedDrivers = ['mysql', 'mysql2', 'pg', 'mssql', 'sqlite3']; const supportedDrivers = ['mysql', 'mysql2', 'pg', 'mssql', 'sqlite3'];
const dbDriver = NcConnectionMgrv2.get(base); const dbDriver = await NcConnectionMgrv2.get(base);
const driverType = dbDriver.clientType(); const driverType = dbDriver.clientType();
// MultiSelect to SingleSelect // MultiSelect to SingleSelect
@ -750,7 +750,7 @@ export async function columnUpdate(param: {
...colBody, ...colBody,
}); });
} else { } else {
colBody = getColumnPropsFromUIDT(colBody, base); colBody = await getColumnPropsFromUIDT(colBody, base);
const tableUpdateBody = { const tableUpdateBody = {
...table, ...table,
tn: table.table_name, tn: table.table_name,
@ -849,7 +849,7 @@ export async function columnAdd(param: {
const project = await base.getProject(); const project = await base.getProject();
if (param.column.title || param.column.column_name) { if (param.column.title || param.column.column_name) {
const dbDriver = NcConnectionMgrv2.get(base); const dbDriver = await NcConnectionMgrv2.get(base);
const sqlClientType = dbDriver.clientType(); const sqlClientType = dbDriver.clientType();
@ -932,7 +932,7 @@ export async function columnAdd(param: {
try { try {
// test the query to see if it is valid in db level // test the query to see if it is valid in db level
const dbDriver = NcConnectionMgrv2.get(base); const dbDriver = await NcConnectionMgrv2.get(base);
await formulaQueryBuilderv2(colBody.formula, null, dbDriver, table); await formulaQueryBuilderv2(colBody.formula, null, dbDriver, table);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@ -947,7 +947,7 @@ export async function columnAdd(param: {
break; break;
default: default:
{ {
colBody = getColumnPropsFromUIDT(colBody, base); colBody = await getColumnPropsFromUIDT(colBody, base);
if (colBody.uidt === UITypes.Duration) { if (colBody.uidt === UITypes.Duration) {
colBody.dtxp = '20'; colBody.dtxp = '20';
// by default, colBody.dtxs is 2 // by default, colBody.dtxs is 2
@ -958,7 +958,7 @@ export async function columnAdd(param: {
if ( if (
[UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt) [UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt)
) { ) {
const dbDriver = NcConnectionMgrv2.get(base); const dbDriver = await NcConnectionMgrv2.get(base);
const driverType = dbDriver.clientType(); const driverType = dbDriver.clientType();
const optionTitles = colBody.colOptions.options.map((el) => const optionTitles = colBody.colOptions.options.map((el) =>
el.title.replace(/'/g, "''") el.title.replace(/'/g, "''")

2
packages/nocodb/src/lib/services/dbData/bulkData.ts

@ -27,7 +27,7 @@ export async function executeBulkOperation<T extends BulkOperation>(
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
return await baseModel[param.operation].apply(null, param.options); return await baseModel[param.operation].apply(null, param.options);
} }

14
packages/nocodb/src/lib/services/dbData/dataAliasNested.svs.ts → packages/nocodb/src/lib/services/dbData/dataAliasNested.svc.ts

@ -25,7 +25,7 @@ export async function mmList(
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const column = await getColumnByIdOrName(param.columnName, model); const column = await getColumnByIdOrName(param.columnName, model);
@ -63,7 +63,7 @@ export async function mmExcludedList(
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const column = await getColumnByIdOrName(param.columnName, model); const column = await getColumnByIdOrName(param.columnName, model);
@ -105,7 +105,7 @@ export async function hmExcludedList(
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const column = await getColumnByIdOrName(param.columnName, model); const column = await getColumnByIdOrName(param.columnName, model);
@ -147,7 +147,7 @@ export async function btExcludedList(
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const column = await getColumnByIdOrName(param.columnName, model); const column = await getColumnByIdOrName(param.columnName, model);
@ -191,7 +191,7 @@ export async function hmList(
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const column = await getColumnByIdOrName(param.columnName, model); const column = await getColumnByIdOrName(param.columnName, model);
@ -232,7 +232,7 @@ export async function relationDataRemove(
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const column = await getColumnByIdOrName(param.columnName, model); const column = await getColumnByIdOrName(param.columnName, model);
@ -265,7 +265,7 @@ export async function relationDataAdd(
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const column = await getColumnByIdOrName(param.columnName, model); const column = await getColumnByIdOrName(param.columnName, model);

4
packages/nocodb/src/lib/services/dbData/helpers.ts

@ -58,7 +58,7 @@ export async function extractXlsxData(view: View, req: Request) {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: view.model.id, id: view.model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const { offset, dbRows, elapsed } = await getDbRows({ const { offset, dbRows, elapsed } = await getDbRows({
@ -93,7 +93,7 @@ export async function extractCsvData(view: View, req: Request) {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: view.model.id, id: view.model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const { offset, dbRows, elapsed } = await getDbRows({ const { offset, dbRows, elapsed } = await getDbRows({

42
packages/nocodb/src/lib/services/dbData/index.ts

@ -30,7 +30,7 @@ export async function dataCount(param: PathParams & { query: any }) {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const countArgs: any = { ...param.query }; const countArgs: any = { ...param.query };
@ -53,7 +53,7 @@ export async function dataInsert(
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
return await baseModel.insert(param.body, null, param.cookie); return await baseModel.insert(param.body, null, param.cookie);
@ -68,7 +68,7 @@ export async function dataUpdate(
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
return await baseModel.updateByPk( return await baseModel.updateByPk(
@ -87,7 +87,7 @@ export async function dataDelete(
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
// todo: Should have error http status code // todo: Should have error http status code
@ -110,7 +110,7 @@ export async function getDataList(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const requestObj = await getAst({ model, query, view }); const requestObj = await getAst({ model, query, view });
@ -158,7 +158,7 @@ export async function getFindOne(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const args: any = { ...query }; const args: any = { ...query };
@ -192,7 +192,7 @@ export async function getDataGroupBy(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const listArgs: any = { ...query }; const listArgs: any = { ...query };
@ -215,7 +215,7 @@ export async function dataRead(
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const row = await baseModel.readByPk(param.rowId); const row = await baseModel.readByPk(param.rowId);
@ -242,7 +242,7 @@ export async function dataExist(
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
return await baseModel.exist(param.rowId); return await baseModel.exist(param.rowId);
@ -275,7 +275,7 @@ export async function getGroupedDataList(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const requestObj = await getAst({ model, query, view }); const requestObj = await getAst({ model, query, view });
@ -353,7 +353,7 @@ export async function mmList(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const key = `${model.title}List`; const key = `${model.title}List`;
@ -411,7 +411,7 @@ export async function mmExcludedList(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const key = 'List'; const key = 'List';
@ -472,7 +472,7 @@ export async function hmExcludedList(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const key = 'List'; const key = 'List';
@ -533,7 +533,7 @@ export async function btExcludedList(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const key = 'List'; const key = 'List';
@ -594,7 +594,7 @@ export async function hmList(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const key = `${model.title}List`; const key = `${model.title}List`;
@ -646,7 +646,7 @@ export async function dataReadByViewId(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
return await nocoExecute( return await nocoExecute(
@ -677,7 +677,7 @@ export async function dataInsertByViewId(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
return await baseModel.insert(param.body, null, param.cookie); return await baseModel.insert(param.body, null, param.cookie);
@ -698,7 +698,7 @@ export async function dataUpdateByViewId(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
return await baseModel.updateByPk( return await baseModel.updateByPk(
@ -723,7 +723,7 @@ export async function dataDeleteByViewId(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
return await baseModel.delByPk(param.rowId, null, param.cookie); return await baseModel.delByPk(param.rowId, null, param.cookie);
@ -749,7 +749,7 @@ export async function relationDataDelete(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
await baseModel.removeChild({ await baseModel.removeChild({
@ -782,7 +782,7 @@ export async function relationDataAdd(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
await baseModel.addChild({ await baseModel.addChild({

0
packages/nocodb/src/lib/services/ee/orgToken.svs.ts → packages/nocodb/src/lib/services/ee/orgToken.svc.ts

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

@ -27,7 +27,7 @@ export * as attachmentService from './attachment.svc';
export * as hookFilterService from './hookFilter.svc'; export * as hookFilterService from './hookFilter.svc';
export * as dataService from './dbData'; export * as dataService from './dbData';
export * as bulkDataService from './dbData/bulkData'; export * as bulkDataService from './dbData/bulkData';
export * as dataAliasNestedService from './dbData/dataAliasNested.svs'; export * as dataAliasNestedService from './dbData/dataAliasNested.svc';
export * as cacheService from './cache.svc'; export * as cacheService from './cache.svc';
export * as auditService from './audit.svc'; export * as auditService from './audit.svc';
export * as swaggerService from './apiDocs'; export * as swaggerService from './apiDocs';
@ -35,4 +35,4 @@ export * as userService from './user';
export * as syncService from './sync'; export * as syncService from './sync';
export * from './public'; export * from './public';
export * as orgTokenService from './orgToken.svc'; export * as orgTokenService from './orgToken.svc';
export * as orgTokenServiceEE from './ee/orgToken.svs'; export * as orgTokenServiceEE from './ee/orgToken.svc';

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

@ -1,5 +1,5 @@
import * as publicDataService from './publicData.svs'; import * as publicDataService from './publicData.svc';
import * as publicDataExportApis from './publicDataExport.svs'; import * as publicDataExportApis from './publicDataExport.svc';
import * as publicMetaService from './publicMeta.svs'; import * as publicMetaService from './publicMeta.svc';
export { publicDataService, publicDataExportApis, publicMetaService }; export { publicDataService, publicDataExportApis, publicMetaService };

12
packages/nocodb/src/lib/services/public/publicData.svs.ts → packages/nocodb/src/lib/services/public/publicData.svc.ts

@ -49,7 +49,7 @@ export async function dataList(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const listArgs: any = { ...param.query }; const listArgs: any = { ...param.query };
@ -130,7 +130,7 @@ async function getGroupedDataList(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const requestObj = await getAst({ model, query: param.query, view }); const requestObj = await getAst({ model, query: param.query, view });
@ -208,7 +208,7 @@ export async function dataInsert(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
await view.getViewWithInfo(); await view.getViewWithInfo();
@ -306,7 +306,7 @@ export async function relDataList(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: model.id, id: model.id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const requestObj = await getAst({ const requestObj = await getAst({
@ -362,7 +362,7 @@ export async function publicMmList(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: view.fk_model_id, id: view.fk_model_id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const key = `List`; const key = `List`;
@ -427,7 +427,7 @@ export async function publicHmList(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: view.fk_model_id, id: view.fk_model_id,
viewId: view?.id, viewId: view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const key = `List`; const key = `List`;

2
packages/nocodb/src/lib/services/public/publicDataExport.svs.ts → packages/nocodb/src/lib/services/public/publicDataExport.svc.ts

@ -45,7 +45,7 @@ export async function getDbRows(param: {
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: param.model.id, id: param.model.id,
viewId: param.view?.id, viewId: param.view?.id,
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
}); });
const requestObj = await getAst({ const requestObj = await getAst({

0
packages/nocodb/src/lib/services/public/publicMeta.svs.ts → packages/nocodb/src/lib/services/public/publicMeta.svc.ts

7
packages/nocodb/src/lib/services/sync/index.ts

@ -15,10 +15,9 @@ export async function syncCreate(param: {
projectId: string; projectId: string;
baseId?: string; baseId?: string;
userId: string; userId: string;
// todo: define type
syncPayload: Partial<SyncSource>; syncPayload: Partial<SyncSource>;
}) { }) {
T.emit('evt', { evt_type: 'webhooks:created' }); T.emit('evt', { evt_type: 'syncSource:created' });
const project = await Project.getWithInfo(param.projectId); const project = await Project.getWithInfo(param.projectId);
const sync = await SyncSource.insert({ const sync = await SyncSource.insert({
@ -31,7 +30,7 @@ export async function syncCreate(param: {
} }
export async function syncDelete(param: { syncId: string }) { export async function syncDelete(param: { syncId: string }) {
T.emit('evt', { evt_type: 'webhooks:deleted' }); T.emit('evt', { evt_type: 'syncSource:deleted' });
return await SyncSource.delete(param.syncId); return await SyncSource.delete(param.syncId);
} }
@ -39,7 +38,7 @@ export async function syncUpdate(param: {
syncId: string; syncId: string;
syncPayload: Partial<SyncSource>; syncPayload: Partial<SyncSource>;
}) { }) {
T.emit('evt', { evt_type: 'webhooks:updated' }); T.emit('evt', { evt_type: 'syncSource:updated' });
return await SyncSource.update(param.syncId, param.syncPayload); return await SyncSource.update(param.syncId, param.syncPayload);
} }

55
packages/nocodb/src/lib/services/table.svc.ts

@ -2,6 +2,7 @@ import DOMPurify from 'isomorphic-dompurify';
import { import {
AuditOperationSubTypes, AuditOperationSubTypes,
AuditOperationTypes, AuditOperationTypes,
ColumnType,
isVirtualCol, isVirtualCol,
ModelTypes, ModelTypes,
NormalColumnRequestType, NormalColumnRequestType,
@ -314,6 +315,12 @@ export async function tableCreate(param: {
}) { }) {
validatePayload('swagger.json#/components/schemas/TableReq', param.table); validatePayload('swagger.json#/components/schemas/TableReq', param.table);
const tableCreatePayLoad: Omit<TableReqType, 'columns'> & {
columns: (Omit<ColumnType, 'column_name' | 'title'> & { cn?: string })[];
} = {
...param.table,
};
const project = await Project.getWithInfo(param.projectId); const project = await Project.getWithInfo(param.projectId);
let base = project.bases[0]; let base = project.bases[0];
@ -322,8 +329,8 @@ export async function tableCreate(param: {
} }
if ( if (
!param.table.table_name || !tableCreatePayLoad.table_name ||
(project.prefix && project.prefix === param.table.table_name) (project.prefix && project.prefix === tableCreatePayLoad.table_name)
) { ) {
NcError.badRequest( NcError.badRequest(
'Missing table name `table_name` property in request body' 'Missing table name `table_name` property in request body'
@ -331,15 +338,17 @@ export async function tableCreate(param: {
} }
if (base.is_meta && project.prefix) { if (base.is_meta && project.prefix) {
if (!param.table.table_name.startsWith(project.prefix)) { if (!tableCreatePayLoad.table_name.startsWith(project.prefix)) {
param.table.table_name = `${project.prefix}_${param.table.table_name}`; tableCreatePayLoad.table_name = `${project.prefix}_${tableCreatePayLoad.table_name}`;
} }
} }
param.table.table_name = DOMPurify.sanitize(param.table.table_name); tableCreatePayLoad.table_name = DOMPurify.sanitize(
tableCreatePayLoad.table_name
);
// validate table name // validate table name
if (/^\s+|\s+$/.test(param.table.table_name)) { if (/^\s+|\s+$/.test(tableCreatePayLoad.table_name)) {
NcError.badRequest( NcError.badRequest(
'Leading or trailing whitespace not allowed in table names' 'Leading or trailing whitespace not allowed in table names'
); );
@ -347,7 +356,7 @@ export async function tableCreate(param: {
if ( if (
!(await Model.checkTitleAvailable({ !(await Model.checkTitleAvailable({
table_name: param.table.table_name, table_name: tableCreatePayLoad.table_name,
project_id: project.id, project_id: project.id,
base_id: base.id, base_id: base.id,
})) }))
@ -355,9 +364,9 @@ export async function tableCreate(param: {
NcError.badRequest('Duplicate table name'); NcError.badRequest('Duplicate table name');
} }
if (!param.table.title) { if (!tableCreatePayLoad.title) {
param.table.title = getTableNameAlias( tableCreatePayLoad.title = getTableNameAlias(
param.table.table_name, tableCreatePayLoad.table_name,
project.prefix, project.prefix,
base base
); );
@ -365,7 +374,7 @@ export async function tableCreate(param: {
if ( if (
!(await Model.checkAliasAvailable({ !(await Model.checkAliasAvailable({
title: param.table.title, title: tableCreatePayLoad.title,
project_id: project.id, project_id: project.id,
base_id: base.id, base_id: base.id,
})) }))
@ -387,7 +396,7 @@ export async function tableCreate(param: {
tableNameLengthLimit = 128; tableNameLengthLimit = 128;
} }
if (param.table.table_name.length > tableNameLengthLimit) { if (tableCreatePayLoad.table_name.length > tableNameLengthLimit) {
NcError.badRequest(`Table name exceeds ${tableNameLengthLimit} characters`); NcError.badRequest(`Table name exceeds ${tableNameLengthLimit} characters`);
} }
@ -401,13 +410,16 @@ export async function tableCreate(param: {
} }
} }
param.table.columns = param.table.columns?.map((c) => ({ tableCreatePayLoad.columns = await Promise.all(
...getColumnPropsFromUIDT(c as any, base), param.table.columns?.map(async (c) => ({
cn: c.column_name, ...(await getColumnPropsFromUIDT(c as any, base)),
})); cn: c.column_name,
column_name: c.column_name,
}))
);
await sqlMgr.sqlOpPlus(base, 'tableCreate', { await sqlMgr.sqlOpPlus(base, 'tableCreate', {
...param.table, ...tableCreatePayLoad,
tn: param.table.table_name, tn: tableCreatePayLoad.table_name,
}); });
const columns: Array< const columns: Array<
@ -415,7 +427,8 @@ export async function tableCreate(param: {
cn: string; cn: string;
system?: boolean; system?: boolean;
} }
> = (await sqlClient.columnList({ tn: param.table.table_name }))?.data?.list; > = (await sqlClient.columnList({ tn: tableCreatePayLoad.table_name }))?.data
?.list;
const tables = await Model.list({ const tables = await Model.list({
project_id: project.id, project_id: project.id,
@ -428,7 +441,7 @@ export async function tableCreate(param: {
op_type: AuditOperationTypes.TABLE, op_type: AuditOperationTypes.TABLE,
op_sub_type: AuditOperationSubTypes.CREATED, op_sub_type: AuditOperationSubTypes.CREATED,
user: param.user?.email, user: param.user?.email,
description: `created table ${param.table.table_name} with alias ${param.table.title} `, description: `created table ${tableCreatePayLoad.table_name} with alias ${tableCreatePayLoad.title} `,
ip: param.req?.clientIp, ip: param.req?.clientIp,
}).then(() => {}); }).then(() => {});
@ -438,7 +451,7 @@ export async function tableCreate(param: {
// todo: type correction // todo: type correction
const result = await Model.insert(project.id, base.id, { const result = await Model.insert(project.id, base.id, {
...param.table, ...tableCreatePayLoad,
columns: columns.map((c, i) => { columns: columns.map((c, i) => {
const colMetaFromReq = param.table?.columns?.find( const colMetaFromReq = param.table?.columns?.find(
(c1) => c.cn === c1.column_name (c1) => c.cn === c1.column_name

10
packages/nocodb/src/lib/services/user/helpers.ts

@ -2,6 +2,7 @@ import * as jwt from 'jsonwebtoken';
import crypto from 'crypto'; import crypto from 'crypto';
import User from '../../models/User'; import User from '../../models/User';
import { NcConfig } from '../../../interface/config'; import { NcConfig } from '../../../interface/config';
import { Response } from 'express';
export function genJwt(user: User, config: NcConfig) { export function genJwt(user: User, config: NcConfig) {
return jwt.sign( return jwt.sign(
@ -21,3 +22,12 @@ export function genJwt(user: User, config: NcConfig) {
export function randomTokenString(): string { export function randomTokenString(): string {
return crypto.randomBytes(40).toString('hex'); return crypto.randomBytes(40).toString('hex');
} }
export 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);
}

164
packages/nocodb/src/lib/services/user/index.ts

@ -2,6 +2,7 @@ import {
PasswordChangeReqType, PasswordChangeReqType,
PasswordForgotReqType, PasswordForgotReqType,
PasswordResetReqType, PasswordResetReqType,
SignUpReqType,
UserType, UserType,
validatePassword, validatePassword,
} from 'nocodb-sdk'; } from 'nocodb-sdk';
@ -19,9 +20,10 @@ import NcPluginMgrv2 from '../../meta/helpers/NcPluginMgrv2';
import { Audit, Store, User } from '../../models'; import { Audit, Store, User } from '../../models';
import Noco from '../../Noco'; import Noco from '../../Noco';
import { MetaTable } from '../../utils/globals'; import { MetaTable } from '../../utils/globals';
import { randomTokenString } from './helpers'; import { genJwt, randomTokenString, setTokenCookie } from './helpers';
const { v4: uuidv4 } = require('uuid'); const { v4: uuidv4 } = require('uuid');
const { isEmail } = require('validator');
export async function registerNewUserIfAllowed({ export async function registerNewUserIfAllowed({
firstname, firstname,
@ -136,7 +138,7 @@ export async function passwordForgot(param: {
req: any; req: any;
}): Promise<any> { }): Promise<any> {
validatePayload( validatePayload(
'swagger.json#/components/schemas/ForgotPasswordReq', 'swagger.json#/components/schemas/PasswordForgotReq',
param.body param.body
); );
@ -296,5 +298,163 @@ export async function emailVerification(param: {
return true; return true;
} }
export async function refreshToken(param: {
body: SignUpReqType;
req: any;
res: any;
}): Promise<any> {
try {
if (!param.req?.cookies?.refresh_token) {
NcError.badRequest(`Missing refresh token`);
}
const user = await User.getByRefreshToken(param.req.cookies.refresh_token);
if (!user) {
NcError.badRequest(`Invalid refresh token`);
}
const refreshToken = randomTokenString();
await User.update(user.id, {
email: user.email,
refresh_token: refreshToken,
});
setTokenCookie(param.res, refreshToken);
return {
token: genJwt(user, Noco.getConfig()),
} as any;
} catch (e) {
NcError.badRequest(e.message);
}
}
export async function signup(param: {
body: SignUpReqType;
req: any;
res: any;
}): Promise<any> {
validatePayload('swagger.json#/components/schemas/SignUpReq', param.body);
const {
email: _email,
firstname,
lastname,
token,
ignore_subscribe,
} = param.req.body;
let { password } = param.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 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:
(param.req as any).ncSiteUrl +
`/email/verify/${user.email_verification_token}`,
}),
});
} catch (e) {
console.log(
'Warning : `mailSend` failed, Please configure emailClient configuration.'
);
}
await promisify((param.req as any).login.bind(param.req))(user);
const refreshToken = randomTokenString();
await User.update(user.id, {
refresh_token: refreshToken,
email: user.email,
});
setTokenCookie(param.res, refreshToken);
user = (param.req as any).user;
await Audit.insert({
op_type: 'AUTHENTICATION',
op_sub_type: 'SIGNUP',
user: user.email,
description: `signed up `,
ip: (param.req as any).clientIp,
});
return {
token: genJwt(user, Noco.getConfig()),
} as any;
}
export * from './helpers'; export * from './helpers';
export { default as initAdminFromEnv } from './initAdminFromEnv'; export { default as initAdminFromEnv } from './initAdminFromEnv';

105
packages/nocodb/src/lib/utils/common/NcConnectionMgrv2.ts

@ -1,10 +1,5 @@
import SqlClientFactory from '../../db/sql-client/lib/SqlClientFactory'; import SqlClientFactory from '../../db/sql-client/lib/SqlClientFactory';
import { XKnex } from '../../db/sql-data-mapper'; import { XKnex } from '../../db/sql-data-mapper';
// import { NcConfig } from '../../../interface/config';
// import fs from 'fs';
// import Knex from 'knex';
// import NcMetaIO from '../meta/NcMetaIO';
import { import {
defaultConnectionConfig, defaultConnectionConfig,
defaultConnectionOptions, defaultConnectionOptions,
@ -19,12 +14,6 @@ export default class NcConnectionMgrv2 {
}; };
} = {}; } = {};
// private static metaKnex: NcMetaIO;
//
// public static setXcMeta(ncMeta: NcMetaIO) {
// this.metaKnex = ncMeta;
// }
public static async destroyAll() { public static async destroyAll() {
for (const projectId in this.connectionRefs) { for (const projectId in this.connectionRefs) {
for (const baseId in this.connectionRefs[projectId]) { for (const baseId in this.connectionRefs[projectId]) {
@ -60,7 +49,9 @@ export default class NcConnectionMgrv2 {
} }
} }
public static get(base: Base): XKnex { // NC_DATA_DB is not available in community version
// make it return Promise<XKnex> to avoid conflicts
public static async get(base: Base): Promise<XKnex> {
if (base.is_meta) return Noco.ncMeta.knex; if (base.is_meta) return Noco.ncMeta.knex;
if (this.connectionRefs?.[base.project_id]?.[base.id]) { if (this.connectionRefs?.[base.project_id]?.[base.id]) {
@ -68,76 +59,26 @@ export default class NcConnectionMgrv2 {
} }
this.connectionRefs[base.project_id] = this.connectionRefs[base.project_id] =
this.connectionRefs?.[base.project_id] || {}; this.connectionRefs?.[base.project_id] || {};
// if (?.prefix && this.metaKnex) {
// this.connectionRefs[projectId][env][dbAlias] = this.metaKnex?.knex;
// } else {
// const connectionConfig = this.getConnectionConfig(config, env, dbAlias);
const connectionConfig = base.getConnectionConfig();
//
// if (
// connectionConfig?.connection?.ssl &&
// typeof connectionConfig?.connection?.ssl === 'object'
// ) {
// if (
// connectionConfig.connection.ssl.caFilePath &&
// !connectionConfig.connection.ssl.ca
// ) {
// connectionConfig.connection.ssl.ca = fs
// .readFileSync(connectionConfig.connection.ssl.caFilePath)
// .toString();
// }
// if (
// connectionConfig.connection.ssl.keyFilePath &&
// !connectionConfig.connection.ssl.key
// ) {
// connectionConfig.connection.ssl.key = fs
// .readFileSync(connectionConfig.connection.ssl.keyFilePath)
// .toString();
// }
// if (
// connectionConfig.connection.ssl.certFilePath &&
// !connectionConfig.connection.ssl.cert
// ) {
// connectionConfig.connection.ssl.cert = fs
// .readFileSync(connectionConfig.connection.ssl.certFilePath)
// .toString();
// }
// }
//
// const isSqlite = connectionConfig?.client === 'sqlite3';
//
// if (connectionConfig?.connection?.port) {
// connectionConfig.connection.port = +connectionConfig.connection.port;
// }
this.connectionRefs[base.project_id][base.id] = XKnex( const connectionConfig = await base.getConnectionConfig();
// isSqlite
// ? (connectionConfig.connection as Knex.Config)
// :
{
...defaultConnectionOptions,
...connectionConfig,
connection: {
...defaultConnectionConfig,
...connectionConfig.connection,
typeCast(_field, next) {
const res = next();
if (res instanceof Buffer) {
return [...res]
.map((v) => ('00' + v.toString(16)).slice(-2))
.join('');
}
return res;
},
},
} as any
);
// if (isSqlite) {
// this.connectionRefs[projectId][env][dbAlias]
// .raw(`PRAGMA journal_mode=WAL;`)
// .then(() => {});
// }
this.connectionRefs[base.project_id][base.id] = XKnex({
...defaultConnectionOptions,
...connectionConfig,
connection: {
...defaultConnectionConfig,
...connectionConfig.connection,
typeCast(_field, next) {
const res = next();
if (res instanceof Buffer) {
return [...res]
.map((v) => ('00' + v.toString(16)).slice(-2))
.join('');
}
return res;
},
},
} as any);
return this.connectionRefs[base.project_id][base.id]; return this.connectionRefs[base.project_id][base.id];
} }
@ -150,10 +91,10 @@ export default class NcConnectionMgrv2 {
// } // }
public static async getSqlClient(base: Base, _knex = null) { public static async getSqlClient(base: Base, _knex = null) {
const knex = _knex || this.get(base); const knex = _knex || (await this.get(base));
return SqlClientFactory.create({ return SqlClientFactory.create({
knex, knex,
...base.getConnectionConfig(), ...(await base.getConnectionConfig()),
}); });
} }
} }

2
packages/nocodb/src/lib/version-upgrader/ncAttachmentUpgrader.ts

@ -66,7 +66,7 @@ export default async function ({ ncMeta }: NcUpgraderCtx) {
const knex: Knex = base.is_meta const knex: Knex = base.is_meta
? ncMeta.knexConnection ? ncMeta.knexConnection
: NcConnectionMgrv2.get(base); : await NcConnectionMgrv2.get(base);
const models = await base.getModels(ncMeta); const models = await base.getModels(ncMeta);
// used in timeout error message // used in timeout error message

2
packages/nocodb/src/lib/version-upgrader/ncAttachmentUpgrader_0104002.ts

@ -58,7 +58,7 @@ export default async function ({ ncMeta }: NcUpgraderCtx) {
const knex: Knex = base.is_meta const knex: Knex = base.is_meta
? ncMeta.knexConnection ? ncMeta.knexConnection
: NcConnectionMgrv2.get(base); : await NcConnectionMgrv2.get(base);
const models = await base.getModels(ncMeta); const models = await base.getModels(ncMeta);
// used in timeout error message // used in timeout error message

44
packages/nocodb/src/schema/swagger.json

@ -129,7 +129,11 @@
"Example 1": { "Example 1": {
"value": { "value": {
"email": "user@example.com", "email": "user@example.com",
"password": "password123456789" "password": "password123456789",
"firstname": "Alice",
"lastname": "Smith",
"token": null,
"ignore_subscribe": 0
} }
} }
} }
@ -13359,9 +13363,15 @@
"examples": [ "examples": [
{ {
"email": "user@example.com", "email": "user@example.com",
"password": "password123456789" "password": "password123456789",
"firstname": "Alice",
"lastname": "Smith",
"token": null,
"ignore_subscribe": 0
} }
], ],
"title": "Signup Request Model",
"type": "object",
"properties": { "properties": {
"email": { "email": {
"description": "Email address of the user", "description": "Email address of the user",
@ -13374,11 +13384,35 @@
"example": "password123456789", "example": "password123456789",
"minLength": 8, "minLength": 8,
"type": "string" "type": "string"
},
"firstname": {
"$ref": "#/components/schemas/StringOrNull",
"x-stoplight": {
"id": "lblivgs8wcsm1"
}
},
"lastname": {
"$ref": "#/components/schemas/StringOrNull",
"x-stoplight": {
"id": "d4341r35tucq3"
}
},
"token": {
"$ref": "#/components/schemas/StringOrNull",
"x-stoplight": {
"id": "otw9jgnr9n7c4"
},
"description": "Sign Up Token. Used for invitation."
},
"ignore_subscribe": {
"$ref": "#/components/schemas/Bool",
"x-stoplight": {
"id": "g7ge6mc6vdsds"
},
"description": "Ignore Subscription"
} }
}, },
"required": ["email", "password"], "required": ["email", "password"]
"title": "Signup Request Model",
"type": "object"
}, },
"Sort": { "Sort": {
"description": "Model for Sort", "description": "Model for Sort",

2
packages/nocodb/tests/unit/factory/row.ts

@ -218,7 +218,7 @@ const listRow = async ({
const bases = await project.getBases(); const bases = await project.getBases();
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: table.id, id: table.id,
dbDriver: NcConnectionMgrv2.get(bases[0]!), dbDriver: await NcConnectionMgrv2.get(bases[0]!),
}); });
const ignorePagination = !options; const ignorePagination = !options;

8
packages/nocodb/tests/unit/model/tests/baseModelSql.test.ts

@ -31,7 +31,7 @@ function baseModelSqlTests() {
const base = await Base.get(table.base_id); const base = await Base.get(table.base_id);
baseModelSql = new BaseModelSqlv2({ baseModelSql = new BaseModelSqlv2({
dbDriver: NcConnectionMgrv2.get(base), dbDriver: await NcConnectionMgrv2.get(base),
model: table, model: table,
view view
}) })
@ -347,7 +347,7 @@ function baseModelSqlTests() {
); );
const childBaseModel = new BaseModelSqlv2({ const childBaseModel = new BaseModelSqlv2({
dbDriver: NcConnectionMgrv2.get(await Base.get(table.base_id)), dbDriver: await NcConnectionMgrv2.get(await Base.get(table.base_id)),
model: childTable, model: childTable,
view view
}) })
@ -406,7 +406,7 @@ function baseModelSqlTests() {
}); });
const childBaseModel = new BaseModelSqlv2({ const childBaseModel = new BaseModelSqlv2({
dbDriver: NcConnectionMgrv2.get(await Base.get(table.base_id)), dbDriver: await NcConnectionMgrv2.get(await Base.get(table.base_id)),
model: childTable, model: childTable,
view view
}) })
@ -473,7 +473,7 @@ function baseModelSqlTests() {
}); });
const childBaseModel = new BaseModelSqlv2({ const childBaseModel = new BaseModelSqlv2({
dbDriver: NcConnectionMgrv2.get(await Base.get(table.base_id)), dbDriver: await NcConnectionMgrv2.get(await Base.get(table.base_id)),
model: childTable, model: childTable,
view view
}) })

2
tests/playwright/tests/filters.spec.ts

@ -9,7 +9,7 @@ import { rowMixedValue } from '../setup/xcdb-records';
let dashboard: DashboardPage, toolbar: ToolbarPage; let dashboard: DashboardPage, toolbar: ToolbarPage;
let context: any; let context: any;
let api: Api<any>; let api: Api<any>;
let records = []; let records: Record<string, any>;
const skipList = { const skipList = {
Number: ['is null', 'is not null'], Number: ['is null', 'is not null'],

Loading…
Cancel
Save