Browse Source

style(nocodb): applied prettier styling

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/642/head
Pranav C 3 years ago
parent
commit
6c923aa12d
  1. 3
      packages/nocodb/.prettierrc.js
  2. 137
      packages/nocodb/src/__tests__/formula.test.ts
  3. 479
      packages/nocodb/src/__tests__/graphql.test.ts
  4. 37
      packages/nocodb/src/__tests__/noco/NcConfigFactory.test.ts
  5. 789
      packages/nocodb/src/__tests__/rest.test.ts
  6. 16
      packages/nocodb/src/example/docker.ts
  7. 38
      packages/nocodb/src/example/try.ts
  8. 19
      packages/nocodb/src/interface/IEmailAdapter.ts
  9. 15
      packages/nocodb/src/interface/IStorageAdapter.ts
  10. 5
      packages/nocodb/src/interface/XcDynamicChanges.ts
  11. 6
      packages/nocodb/src/interface/XcMetaMgr.ts
  12. 163
      packages/nocodb/src/interface/config.ts
  13. 8
      packages/nocodb/src/lib/dataMapper/index.ts
  14. 670
      packages/nocodb/src/lib/dataMapper/lib/BaseModel.ts
  15. 12
      packages/nocodb/src/lib/dataMapper/lib/DbFactory.ts
  16. 794
      packages/nocodb/src/lib/dataMapper/lib/sql/CustomKnex.ts
  17. 103
      packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilderFromString.ts
  18. 80
      packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/commonFns.ts
  19. 63
      packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/mssql.ts
  20. 41
      packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/mysql.ts
  21. 29
      packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/pg.ts
  22. 47
      packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/sqlite.ts
  23. 29
      packages/nocodb/src/lib/dataMapper/lib/sql/genRollupSelect.ts
  24. 34
      packages/nocodb/src/lib/dataMapper/lib/sql/mapFunctionName.ts
  25. 9
      packages/nocodb/src/lib/index.ts
  26. 660
      packages/nocodb/src/lib/migrator/SqlMigrator/lib/KnexMigrator.ts
  27. 5
      packages/nocodb/src/lib/migrator/SqlMigrator/lib/SqlMigrator.ts
  28. 16
      packages/nocodb/src/lib/migrator/SqlMigrator/lib/SqlMigratorFactory.ts
  29. 42
      packages/nocodb/src/lib/migrator/SqlMigrator/lib/templates/mssql.template.ts
  30. 34
      packages/nocodb/src/lib/migrator/SqlMigrator/lib/templates/mysql.template.ts
  31. 34
      packages/nocodb/src/lib/migrator/SqlMigrator/lib/templates/pg.template.ts
  32. 28
      packages/nocodb/src/lib/migrator/SqlMigrator/lib/templates/sqlite.template.ts
  33. 37
      packages/nocodb/src/lib/migrator/util/Debug.ts
  34. 24
      packages/nocodb/src/lib/migrator/util/DebugMgr.ts
  35. 12
      packages/nocodb/src/lib/migrator/util/FileCollection.ts
  36. 8
      packages/nocodb/src/lib/migrator/util/Result.ts
  37. 4
      packages/nocodb/src/lib/migrator/util/emit.ts
  38. 29
      packages/nocodb/src/lib/migrator/util/file.help.ts
  39. 434
      packages/nocodb/src/lib/noco/NcProjectBuilder.ts
  40. 42
      packages/nocodb/src/lib/noco/NcProjectBuilderEE.ts
  41. 306
      packages/nocodb/src/lib/noco/Noco.ts
  42. 1724
      packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts
  43. 17
      packages/nocodb/src/lib/noco/common/BaseProcedure.ts
  44. 138
      packages/nocodb/src/lib/noco/common/NcConnectionMgr.ts
  45. 19
      packages/nocodb/src/lib/noco/common/XcAudit.ts
  46. 33
      packages/nocodb/src/lib/noco/common/XcCron.ts
  47. 53
      packages/nocodb/src/lib/noco/common/XcProcedure.ts
  48. 2
      packages/nocodb/src/lib/noco/common/formSubmissionEmailTemplate.ts
  49. 19
      packages/nocodb/src/lib/noco/common/helpers/addErrorOnColumnDeleteInFormula.ts
  50. 48
      packages/nocodb/src/lib/noco/common/helpers/jsepTreeToFormula.ts
  51. 23
      packages/nocodb/src/lib/noco/common/helpers/updateColumnNameInFormula.ts
  52. 2503
      packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts
  53. 411
      packages/nocodb/src/lib/noco/gql/GqlAuthResolver.ts
  54. 22
      packages/nocodb/src/lib/noco/gql/GqlBaseResolver.ts
  55. 27
      packages/nocodb/src/lib/noco/gql/GqlCommonResolvers.ts
  56. 107
      packages/nocodb/src/lib/noco/gql/GqlMiddleware.ts
  57. 59
      packages/nocodb/src/lib/noco/gql/GqlProcedureResolver.ts
  58. 215
      packages/nocodb/src/lib/noco/gql/GqlResolver.ts
  59. 3
      packages/nocodb/src/lib/noco/gql/auth/schema.ts
  60. 3
      packages/nocodb/src/lib/noco/gql/common.schema.ts
  61. 2
      packages/nocodb/src/lib/noco/gql/emailTemplate/forgotPassword.ts
  62. 2
      packages/nocodb/src/lib/noco/gql/emailTemplate/verify.ts
  63. 30
      packages/nocodb/src/lib/noco/meta/MetaAPILogger.ts
  64. 285
      packages/nocodb/src/lib/noco/meta/NcMetaIO.ts
  65. 444
      packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts
  66. 12
      packages/nocodb/src/lib/noco/meta/NcMetaIOImplEE.ts
  67. 161
      packages/nocodb/src/lib/noco/migrations/nc_001_init.ts
  68. 58
      packages/nocodb/src/lib/noco/migrations/nc_002_add_m2m.ts
  69. 14
      packages/nocodb/src/lib/noco/migrations/nc_003_add_fkn_column.ts
  70. 43
      packages/nocodb/src/lib/noco/nc.try.ts
  71. 111
      packages/nocodb/src/lib/noco/plugins/NcPluginMgr.ts
  72. 13
      packages/nocodb/src/lib/noco/plugins/adapters/cache/XcCache.ts
  73. 21
      packages/nocodb/src/lib/noco/plugins/adapters/discord/Discord.ts
  74. 12
      packages/nocodb/src/lib/noco/plugins/adapters/email/EmailFactory.ts
  75. 18
      packages/nocodb/src/lib/noco/plugins/adapters/email/SES.ts
  76. 27
      packages/nocodb/src/lib/noco/plugins/adapters/email/SMTP.ts
  77. 21
      packages/nocodb/src/lib/noco/plugins/adapters/mattermost/Mattermost.ts
  78. 19
      packages/nocodb/src/lib/noco/plugins/adapters/slack/Slack.ts
  79. 24
      packages/nocodb/src/lib/noco/plugins/adapters/storage/Local.ts
  80. 20
      packages/nocodb/src/lib/noco/plugins/adapters/twilio/Twilio.ts
  81. 84
      packages/nocodb/src/lib/noco/plugins/azure.ts
  82. 35
      packages/nocodb/src/lib/noco/plugins/brand.ts
  83. 69
      packages/nocodb/src/lib/noco/plugins/cache.ts
  84. 72
      packages/nocodb/src/lib/noco/plugins/discord.ts
  85. 54
      packages/nocodb/src/lib/noco/plugins/ee.ts
  86. 81
      packages/nocodb/src/lib/noco/plugins/githubAuth.ts
  87. 81
      packages/nocodb/src/lib/noco/plugins/googleAuth.ts
  88. 72
      packages/nocodb/src/lib/noco/plugins/mattermost.ts
  89. 123
      packages/nocodb/src/lib/noco/plugins/ses.ts
  90. 69
      packages/nocodb/src/lib/noco/plugins/slack.ts
  91. 129
      packages/nocodb/src/lib/noco/plugins/smtp.ts
  92. 85
      packages/nocodb/src/lib/noco/plugins/twilio.ts
  93. 2248
      packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts
  94. 1390
      packages/nocodb/src/lib/noco/rest/RestAuthCtrl.ts
  95. 233
      packages/nocodb/src/lib/noco/rest/RestAuthCtrlEE.ts
  96. 89
      packages/nocodb/src/lib/noco/rest/RestBaseCtrl.ts
  97. 156
      packages/nocodb/src/lib/noco/rest/RestCtrl.ts
  98. 141
      packages/nocodb/src/lib/noco/rest/RestCtrlBelongsTo.ts
  99. 33
      packages/nocodb/src/lib/noco/rest/RestCtrlCustom.ts
  100. 146
      packages/nocodb/src/lib/noco/rest/RestCtrlHasMany.ts
  101. Some files were not shown because too many files have changed in this diff Show More

3
packages/nocodb/.prettierrc.js

@ -2,5 +2,6 @@ module.exports = {
semi: true, semi: true,
trailingComma: "all", trailingComma: "all",
singleQuote: true, singleQuote: true,
tabWidth: 2 tabWidth: 2,
printWidth: 120
}; };

137
packages/nocodb/src/__tests__/formula.test.ts

@ -1,59 +1,126 @@
import {expect} from 'chai'; import { expect } from 'chai';
import 'mocha'; import 'mocha';
import knex from '../lib/dataMapper/lib/sql/CustomKnex'; import knex from '../lib/dataMapper/lib/sql/CustomKnex';
import formulaQueryBuilderFromString from "../lib/dataMapper/lib/sql/formulaQueryBuilderFromString"; import formulaQueryBuilderFromString from '../lib/dataMapper/lib/sql/formulaQueryBuilderFromString';
process.env.TEST = 'test'; process.env.TEST = 'test';
describe('{Auth, CRUD, HasMany, Belongs} Tests', () => { describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
let knexMysqlRef; let knexMysqlRef;
let knexPgRef; let knexPgRef;
let knexMssqlRef; let knexMssqlRef;
let knexSqliteRef; let knexSqliteRef;
// Called once before any of the tests in this block begin. // Called once before any of the tests in this block begin.
before(function (done) { before(function(done) {
knexMysqlRef = knex({client:'mysql2'}) knexMysqlRef = knex({ client: 'mysql2' });
knexMssqlRef = knex({client:'mssql'}) knexMssqlRef = knex({ client: 'mssql' });
knexPgRef = knex({client:'pg'}) knexPgRef = knex({ client: 'pg' });
knexSqliteRef = knex({client:'sqlite3'}) knexSqliteRef = knex({ client: 'sqlite3' });
done() done();
}); });
after(done => {
after((done) => {
done(); done();
}); });
describe('Formulas', function() {
describe('Formulas', function () { it('Simple formula', function(done) {
expect(
it('Simple formula', function (done) { formulaQueryBuilderFromString(
expect(formulaQueryBuilderFromString("concat(city, ' _ ',city_id+country_id)", 'a',knexMysqlRef).toQuery()).eq('concat(`city`,\' _ \',`city_id` + `country_id`) as a') "concat(city, ' _ ',city_id+country_id)",
expect(formulaQueryBuilderFromString("concat(city, ' _ ',city_id+country_id)", 'a',knexPgRef).toQuery()).eq('concat("city",\' _ \',"city_id" + "country_id") as a') 'a',
expect(formulaQueryBuilderFromString("concat(city, ' _ ',city_id+country_id)", 'a',knexMssqlRef).toQuery()).eq('concat([city],\' _ \',[city_id] + [country_id]) as a') knexMysqlRef
expect(formulaQueryBuilderFromString("concat(city, ' _ ',city_id+country_id)", 'a',knexSqliteRef).toQuery()).eq('`city` || \' _ \' || (`city_id` + `country_id`) as a') ).toQuery()
done() ).eq("concat(`city`,' _ ',`city_id` + `country_id`) as a");
expect(
formulaQueryBuilderFromString(
"concat(city, ' _ ',city_id+country_id)",
'a',
knexPgRef
).toQuery()
).eq('concat("city",\' _ \',"city_id" + "country_id") as a');
expect(
formulaQueryBuilderFromString(
"concat(city, ' _ ',city_id+country_id)",
'a',
knexMssqlRef
).toQuery()
).eq("concat([city],' _ ',[city_id] + [country_id]) as a");
expect(
formulaQueryBuilderFromString(
"concat(city, ' _ ',city_id+country_id)",
'a',
knexSqliteRef
).toQuery()
).eq("`city` || ' _ ' || (`city_id` + `country_id`) as a");
done();
}); });
it('Addition', function (done) { it('Addition', function(done) {
expect(formulaQueryBuilderFromString("ADD(city_id,country_id,2,3,4,5,4)", 'a',knexMysqlRef).toQuery()).eq('`city_id` + `country_id` + 2 + 3 + 4 + 5 + 4 as a') expect(
expect(formulaQueryBuilderFromString("ADD(city_id,country_id,2,3,4,5,4)", 'a',knexPgRef).toQuery()).eq('"city_id" + "country_id" + 2 + 3 + 4 + 5 + 4 as a') formulaQueryBuilderFromString(
expect(formulaQueryBuilderFromString("ADD(city_id,country_id,2,3,4,5,4)", 'a',knexMssqlRef).toQuery()).eq('[city_id] + [country_id] + 2 + 3 + 4 + 5 + 4 as a') 'ADD(city_id,country_id,2,3,4,5,4)',
expect(formulaQueryBuilderFromString("ADD(city_id,country_id,2,3,4,5,4)", 'a',knexSqliteRef).toQuery()).eq('`city_id` + `country_id` + 2 + 3 + 4 + 5 + 4 as a') 'a',
done() knexMysqlRef
).toQuery()
).eq('`city_id` + `country_id` + 2 + 3 + 4 + 5 + 4 as a');
expect(
formulaQueryBuilderFromString(
'ADD(city_id,country_id,2,3,4,5,4)',
'a',
knexPgRef
).toQuery()
).eq('"city_id" + "country_id" + 2 + 3 + 4 + 5 + 4 as a');
expect(
formulaQueryBuilderFromString(
'ADD(city_id,country_id,2,3,4,5,4)',
'a',
knexMssqlRef
).toQuery()
).eq('[city_id] + [country_id] + 2 + 3 + 4 + 5 + 4 as a');
expect(
formulaQueryBuilderFromString(
'ADD(city_id,country_id,2,3,4,5,4)',
'a',
knexSqliteRef
).toQuery()
).eq('`city_id` + `country_id` + 2 + 3 + 4 + 5 + 4 as a');
done();
}); });
it('Average', function (done) { it('Average', function(done) {
expect(formulaQueryBuilderFromString("AVG(city_id,country_id,2,3,4,5,4)", 'a',knexMysqlRef).toQuery()).eq('(`city_id` + `country_id` + 2 + 3 + 4 + 5 + 4) / 7 as a') expect(
expect(formulaQueryBuilderFromString("AVG(city_id,country_id,2,3,4,5,4)", 'a',knexPgRef).toQuery()).eq('("city_id" + "country_id" + 2 + 3 + 4 + 5 + 4) / 7 as a') formulaQueryBuilderFromString(
expect(formulaQueryBuilderFromString("AVG(city_id,country_id,2,3,4,5,4)", 'a',knexMssqlRef).toQuery()).eq('([city_id] + [country_id] + 2 + 3 + 4 + 5 + 4) / 7 as a') 'AVG(city_id,country_id,2,3,4,5,4)',
expect(formulaQueryBuilderFromString("AVG(city_id,country_id,2,3,4,5,4)", 'a',knexSqliteRef).toQuery()).eq('(`city_id` + `country_id` + 2 + 3 + 4 + 5 + 4) / 7 as a') 'a',
done() knexMysqlRef
).toQuery()
).eq('(`city_id` + `country_id` + 2 + 3 + 4 + 5 + 4) / 7 as a');
expect(
formulaQueryBuilderFromString(
'AVG(city_id,country_id,2,3,4,5,4)',
'a',
knexPgRef
).toQuery()
).eq('("city_id" + "country_id" + 2 + 3 + 4 + 5 + 4) / 7 as a');
expect(
formulaQueryBuilderFromString(
'AVG(city_id,country_id,2,3,4,5,4)',
'a',
knexMssqlRef
).toQuery()
).eq('([city_id] + [country_id] + 2 + 3 + 4 + 5 + 4) / 7 as a');
expect(
formulaQueryBuilderFromString(
'AVG(city_id,country_id,2,3,4,5,4)',
'a',
knexSqliteRef
).toQuery()
).eq('(`city_id` + `country_id` + 2 + 3 + 4 + 5 + 4) / 7 as a');
done();
}); });
}); });
});
});/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>

479
packages/nocodb/src/__tests__/graphql.test.ts

@ -1,76 +1,77 @@
import {expect} from 'chai'; import { expect } from 'chai';
import 'mocha'; import 'mocha';
import express from 'express'; import express from 'express';
import request from 'supertest'; import request from 'supertest';
import {Noco} from "../lib"; import { Noco } from '../lib';
import NcConfigFactory from "../lib/utils/NcConfigFactory"; import NcConfigFactory from '../lib/utils/NcConfigFactory';
process.env.TEST = 'test'; process.env.TEST = 'test';
const dbConfig = NcConfigFactory.urlToDbConfig(
const dbConfig = NcConfigFactory.urlToDbConfig(NcConfigFactory.extractXcUrlFromJdbc(process.env[`DATABASE_URL`]), null, null, 'graphql'); NcConfigFactory.extractXcUrlFromJdbc(process.env[`DATABASE_URL`]),
null,
null,
'graphql'
);
const projectCreateReqBody = { const projectCreateReqBody = {
"api": "projectCreateByWeb", api: 'projectCreateByWeb',
"query": {"skipProjectHasDb": 1}, query: { skipProjectHasDb: 1 },
"args": { args: {
"project": {"title": "sebulba", "folder": "config.xc.json", "type": "pg"}, project: { title: 'sebulba', folder: 'config.xc.json', type: 'pg' },
"projectJson": { projectJson: {
"title": "sebulba", title: 'sebulba',
"version": "0.6", version: '0.6',
"envs": { envs: {
"_noco": { _noco: {
"db": [ db: [dbConfig],
dbConfig apiClient: { data: [] }
], "apiClient": {"data": []}
} }
}, },
"workingEnv": "_noco", workingEnv: '_noco',
"meta": { meta: {
"version": "0.6", version: '0.6',
"seedsFolder": "seeds", seedsFolder: 'seeds',
"queriesFolder": "queries", queriesFolder: 'queries',
"apisFolder": "apis", apisFolder: 'apis',
"projectType": "graphql", projectType: 'graphql',
"type": "mvc", type: 'mvc',
"language": "ts", language: 'ts',
"db": {"client": "sqlite3", "connection": {"filename": "noco.db"}} db: { client: 'sqlite3', connection: { filename: 'noco.db' } }
}, },
"seedsFolder": "seeds", seedsFolder: 'seeds',
"queriesFolder": "queries", queriesFolder: 'queries',
"apisFolder": "apis", apisFolder: 'apis',
"projectType": "graphql", projectType: 'graphql',
"type": "docker", type: 'docker',
"language": "ts", language: 'ts',
"apiClient": {"data": []}, apiClient: { data: [] },
"auth": {"jwt": {"secret": "b8ed266d-4475-4028-8c3d-590f58bee867", "dbAlias": "db"}} auth: {
jwt: { secret: 'b8ed266d-4475-4028-8c3d-590f58bee867', dbAlias: 'db' }
}
} }
} }
} };
describe('{Auth, CRUD, HasMany, Belongs} Tests', () => { describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
let app; let app;
let token; let token;
let projectId; let projectId;
// Called once before any of the tests in this block begin. // Called once before any of the tests in this block begin.
before(function (done) { before(function(done) {
this.timeout(10000); this.timeout(10000);
(async () => { (async () => {
const server = express(); const server = express();
server.use(await Noco.init({})); server.use(await Noco.init({}));
app = server; app = server;
})()
})().then(done).catch(done); .then(done)
.catch(done);
}); });
after(done => {
after((done) => {
done(); done();
// process.exit(); // process.exit();
}); });
@ -233,39 +234,39 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
// /**** Authentication : END ****/ // /**** Authentication : END ****/
/**************** START : Auth ****************/ /**************** START : Auth ****************/
describe('Authentication', function () { describe('Authentication', function() {
this.timeout(10000); this.timeout(10000);
const EMAIL_ID = 'abc@g.com' const EMAIL_ID = 'abc@g.com';
const VALID_PASSWORD = '1234566778'; const VALID_PASSWORD = '1234566778';
it('Signup with valid email', function (done) { it('Signup with valid email', function(done) {
this.timeout(20000) this.timeout(20000);
request(app) request(app)
.post('/auth/signup') .post('/auth/signup')
.send({email: EMAIL_ID, password: VALID_PASSWORD}) .send({ email: EMAIL_ID, password: VALID_PASSWORD })
.expect(200, (err, res) => { .expect(200, (err, res) => {
if (err) { if (err) {
expect(res.status).to.equal(400) expect(res.status).to.equal(400);
} else { } else {
const token = res.body.token; const token = res.body.token;
expect(token).to.be.a("string") expect(token).to.be.a('string');
} }
done(); done();
}); });
}); });
it('Signup with invalid email', (done) => { it('Signup with invalid email', done => {
request(app) request(app)
.post('/auth/signup') .post('/auth/signup')
.send({email: 'test', password: VALID_PASSWORD}) .send({ email: 'test', password: VALID_PASSWORD })
.expect(400, done); .expect(400, done);
}); });
it('Signin with valid credentials', function (done) { it('Signin with valid credentials', function(done) {
request(app) request(app)
.post('/auth/signin') .post('/auth/signin')
.send({email: EMAIL_ID, password: VALID_PASSWORD}) .send({ email: EMAIL_ID, password: VALID_PASSWORD })
.expect(200, async function (err, res) { .expect(200, async function(err, res) {
if (err) { if (err) {
return done(err); return done(err);
} }
@ -279,11 +280,11 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
}); });
}); });
it('me', function (done) { it('me', function(done) {
request(app) request(app)
.get('/user/me') .get('/user/me')
.set('xc-auth', token) .set('xc-auth', token)
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) { if (err) {
return done(err); return done(err);
} }
@ -293,68 +294,62 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
}); });
}); });
it('Change password', function(done) {
it('Change password', function (done) {
request(app) request(app)
.post('/user/password/change') .post('/user/password/change')
.set('xc-auth', token) .set('xc-auth', token)
.send({currentPassword: 'password', newPassword: 'password'}) .send({ currentPassword: 'password', newPassword: 'password' })
.expect(400, done); .expect(400, done);
}); });
it('Change password - after logout', function(done) {
it('Change password - after logout', function (done) {
// todo: // todo:
request(app) request(app)
.post('/user/password/change') .post('/user/password/change')
.send({currentPassword: 'password', newPassword: 'password'}) .send({ currentPassword: 'password', newPassword: 'password' })
.expect(500, function (_err, _res) { .expect(500, function(_err, _res) {
done() done();
}); });
}); });
it('Signin with invalid credentials', function(done) {
it('Signin with invalid credentials', function (done) {
request(app) request(app)
.post('/auth/signin') .post('/auth/signin')
.send({email: 'abc@abc.com', password: VALID_PASSWORD}) .send({ email: 'abc@abc.com', password: VALID_PASSWORD })
.expect(400, done); .expect(400, done);
}); });
it('Signin with invalid password', function (done) { it('Signin with invalid password', function(done) {
request(app) request(app)
.post('/auth/signin') .post('/auth/signin')
.send({email: EMAIL_ID, password: 'wrongPassword'}) .send({ email: EMAIL_ID, password: 'wrongPassword' })
.expect(400, done); .expect(400, done);
}); });
it('Forgot password with a non-existing email id', function(done) {
it('Forgot password with a non-existing email id', function (done) {
request(app) request(app)
.post('/auth/password/forgot') .post('/auth/password/forgot')
.send({email: 'abc@abc.com'}) .send({ email: 'abc@abc.com' })
.expect(400, done); .expect(400, done);
}); });
it('Forgot password with an existing email id', function (done) { it('Forgot password with an existing email id', function(done) {
this.timeout(10000) this.timeout(10000);
request(app) request(app)
.post('/auth/password/forgot') .post('/auth/password/forgot')
.send({email: EMAIL_ID}) .send({ email: EMAIL_ID })
.expect(200, done); .expect(200, done);
}); });
it('Email validate with an invalid token', function (done) { it('Email validate with an invalid token', function(done) {
request(app) request(app)
.post('/auth/email/validate/someRandomValue') .post('/auth/email/validate/someRandomValue')
.send({email: EMAIL_ID}) .send({ email: EMAIL_ID })
.expect(400, done); .expect(400, done);
}); });
it('Email validate with a valid token', function (done) { it('Email validate with a valid token', function(done) {
console.log('eeee');
console.log('eeee')
// todo : // todo :
done(); done();
@ -365,18 +360,17 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
// .expect(500, done); // .expect(500, done);
}); });
it('Forgot password validate with an invalid token', function(done) {
it('Forgot password validate with an invalid token', function (done) {
request(app) request(app)
.post('/auth/token/validate/someRandomValue') .post('/auth/token/validate/someRandomValue')
.send({email: EMAIL_ID}) .send({ email: EMAIL_ID })
.expect(400, done); .expect(400, done);
}); });
it('Forgot password validate with a valid token', function (done) { it('Forgot password validate with a valid token', function(done) {
// todo // todo
done() done();
// request(app) // request(app)
// .post('/auth/token/validate/someRandomValue') // .post('/auth/token/validate/someRandomValue')
@ -384,38 +378,34 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
// .expect(500, done); // .expect(500, done);
}); });
it('Reset Password with an invalid token', function(done) {
it('Reset Password with an invalid token', function (done) {
request(app) request(app)
.post('/auth/password/reset/someRandomValue') .post('/auth/password/reset/someRandomValue')
.send({password: 'anewpassword'}) .send({ password: 'anewpassword' })
.expect(400, done); .expect(400, done);
}); });
it('Reset Password with an valid token', function (done) { it('Reset Password with an valid token', function(done) {
//todo //todo
done() done();
// request(app) // request(app)
// .post('/auth/password/reset/someRandomValue') // .post('/auth/password/reset/someRandomValue')
// .send({password: 'anewpassword'}) // .send({password: 'anewpassword'})
// .expect(500, done); // .expect(500, done);
}); });
}); });
describe('Project', function () { describe('Project', function() {
const EMAIL_ID = 'abc@g.com' const EMAIL_ID = 'abc@g.com';
const VALID_PASSWORD = '1234566778'; const VALID_PASSWORD = '1234566778';
before(function(done) {
before(function (done) { this.timeout(120000);
this.timeout(120000)
request(app) request(app)
.post('/auth/signin') .post('/auth/signin')
.send({email: EMAIL_ID, password: VALID_PASSWORD}) .send({ email: EMAIL_ID, password: VALID_PASSWORD })
.expect(200, async function (_err, res) { .expect(200, async function(_err, res) {
token = res.body.token; token = res.body.token;
request(app) request(app)
.post('/dashboard') .post('/dashboard')
@ -423,34 +413,33 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send(projectCreateReqBody) .send(projectCreateReqBody)
.expect(200, (err, res) => { .expect(200, (err, res) => {
if (err) { if (err) {
return done(err) return done(err);
} }
projectId = res.body.id; projectId = res.body.id;
done(); done();
}) });
}); });
}) });
/**** country : START ****/ /**** country : START ****/
describe('country', function () { describe('country', function() {
/**** Query : START ****/ /**** Query : START ****/
it('countryList', function (done) { it('countryList', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
.set('xc-auth', token) .set('xc-auth', token)
.send({ .send({
query: `{ countryList(limit:5){ country_id country } }` query: `{ countryList(limit:5){ country_id country } }`
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const list = res.body.data.countryList; const list = res.body.data.countryList;
expect(list).length.to.be.most(5) expect(list).length.to.be.most(5);
expect(list[0]).to.have.all.keys(['country_id', 'country']) expect(list[0]).to.have.all.keys(['country_id', 'country']);
done() done();
}) });
}); });
it('countryList - with sort', function (done) { it('countryList - with sort', function(done) {
// todo: order -> sort // todo: order -> sort
request(app) request(app)
@ -460,24 +449,24 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryList(sort:"-country_id"){ country_id country } }` query: `{ countryList(sort:"-country_id"){ country_id country } }`
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const list = res.body.data.countryList; const list = res.body.data.countryList;
expect(list[0]).to.have.all.keys(['country_id', 'country']) expect(list[0]).to.have.all.keys(['country_id', 'country']);
expect(list).satisfy(array => { expect(list).satisfy(array => {
let i = array.length; let i = array.length;
while (--i) { while (--i) {
if (array[i].country_id > array[i - 1].country_id) return false; if (array[i].country_id > array[i - 1].country_id) return false;
} }
return true return true;
}, 'Should be in descending order') }, 'Should be in descending order');
done() done();
}) });
}); });
it('countryList - with limit', function (done) { it('countryList - with limit', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -485,16 +474,16 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryList(limit:6){ country_id country } }` query: `{ countryList(limit:6){ country_id country } }`
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const list = res.body.data.countryList; const list = res.body.data.countryList;
expect(list[0]).to.have.all.keys(['country_id', 'country']) expect(list[0]).to.have.all.keys(['country_id', 'country']);
expect(list).to.have.length.most(6) expect(list).to.have.length.most(6);
done() done();
}) });
}); });
it('countryList - with offset', function (done) { it('countryList - with offset', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -502,10 +491,10 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryList(offset:0,limit:6){ country_id country } }` query: `{ countryList(offset:0,limit:6){ country_id country } }`
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const list1 = res.body.data.countryList; const list1 = res.body.data.countryList;
expect(list1[0]).to.have.all.keys(['country_id', 'country']) expect(list1[0]).to.have.all.keys(['country_id', 'country']);
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -513,20 +502,26 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryList(offset:1,limit:5){ country_id country } }` query: `{ countryList(offset:1,limit:5){ country_id country } }`
}) })
.expect(200, function (err, res1) { .expect(200, function(err, res1) {
if (err) done(err); if (err) done(err);
const list2 = res1.body.data.countryList; const list2 = res1.body.data.countryList;
expect(list2[0]).to.have.all.keys(['country_id', 'country']) expect(list2[0]).to.have.all.keys(['country_id', 'country']);
expect(list2).satisfy(arr => arr.every(({country, country_id}, i) => expect(list2).satisfy(
country === list1[i + 1].country && country_id === list1[i + 1].country_id arr =>
), 'Both data should need to be equal where offset vary with 1') arr.every(
({ country, country_id }, i) =>
done() country === list1[i + 1].country &&
country_id === list1[i + 1].country_id
),
'Both data should need to be equal where offset vary with 1'
);
done();
}); });
}) });
}); });
it('countryList - nested count', function (done) { it('countryList - nested count', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -534,17 +529,21 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryList{ country_id country cityCount} }` query: `{ countryList{ country_id country cityCount} }`
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const list = res.body.data.countryList; const list = res.body.data.countryList;
expect(list[0]).to.have.all.keys(['country_id', 'country', 'cityCount']) expect(list[0]).to.have.all.keys([
expect(list[0].cityCount).to.be.a('number') 'country_id',
'country',
'cityCount'
]);
expect(list[0].cityCount).to.be.a('number');
expect(list[0].cityCount % 1).to.be.equal(0); expect(list[0].cityCount % 1).to.be.equal(0);
done() done();
}) });
}); });
it('countryList - nested cityList', function (done) { it('countryList - nested cityList', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -552,22 +551,31 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryList{ country_id country cityList { city country_id }} }` query: `{ countryList{ country_id country cityList { city country_id }} }`
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const list = res.body.data.countryList; const list = res.body.data.countryList;
expect(list[0]).to.have.all.keys(['country_id', 'country', 'cityList']) expect(list[0]).to.have.all.keys([
expect(list[0].cityList).to.be.a('Array') 'country_id',
'country',
'cityList'
]);
expect(list[0].cityList).to.be.a('Array');
if (dbConfig.client !== 'mssql') { if (dbConfig.client !== 'mssql') {
expect(list[0].cityList[0]).to.be.a('object'); expect(list[0].cityList[0]).to.be.a('object');
expect(list[0].cityList[0]).to.have.all.keys(['country_id', 'city']) expect(list[0].cityList[0]).to.have.all.keys([
expect(Object.keys(list[0].cityList[0])).to.have.length(2) 'country_id',
expect(list[0].cityList[0].country_id).to.be.equal(list[0].country_id) 'city'
]);
expect(Object.keys(list[0].cityList[0])).to.have.length(2);
expect(list[0].cityList[0].country_id).to.be.equal(
list[0].country_id
);
} }
done() done();
}) });
}); });
it('countryRead', function (done) { it('countryRead', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -575,17 +583,17 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryRead(id: "1"){ country_id country } } ` query: `{ countryRead(id: "1"){ country_id country } } `
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const data = res.body.data.countryRead; const data = res.body.data.countryRead;
expect(data).to.be.a('object') expect(data).to.be.a('object');
expect(data).to.have.all.keys(['country_id', 'country']) expect(data).to.have.all.keys(['country_id', 'country']);
done() done();
}) });
}); });
it('countryExists', function (done) { it('countryExists', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -593,16 +601,16 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryExists(id: "1") } ` query: `{ countryExists(id: "1") } `
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const data = res.body.data.countryExists; const data = res.body.data.countryExists;
expect(data).to.be.a('boolean') expect(data).to.be.a('boolean');
expect(data).to.be.equal(true) expect(data).to.be.equal(true);
done() done();
}) });
}); });
it('countryExists - with non-existing id', function (done) { it('countryExists - with non-existing id', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -610,16 +618,16 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryExists(id: "30000") } ` query: `{ countryExists(id: "30000") } `
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const data = res.body.data.countryExists; const data = res.body.data.countryExists;
expect(data).to.be.a('boolean') expect(data).to.be.a('boolean');
expect(data).to.be.equal(false) expect(data).to.be.equal(false);
done() done();
}) });
}); });
it('countryFindOne', function (done) { it('countryFindOne', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -627,17 +635,17 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryFindOne (where: "(country_id,eq,1)"){ country country_id } } ` query: `{ countryFindOne (where: "(country_id,eq,1)"){ country country_id } } `
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const data = res.body.data.countryFindOne; const data = res.body.data.countryFindOne;
expect(data).to.be.a('object') expect(data).to.be.a('object');
expect(data).to.have.all.keys(['country', 'country_id']) expect(data).to.have.all.keys(['country', 'country_id']);
expect(data.country_id).to.be.equal(1); expect(data.country_id).to.be.equal(1);
done() done();
}) });
}); });
it('countryCount - filter by id', function (done) { it('countryCount - filter by id', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -645,16 +653,16 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryCount (where: "(country_id,eq,1)") } ` query: `{ countryCount (where: "(country_id,eq,1)") } `
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const data = res.body.data.countryCount; const data = res.body.data.countryCount;
expect(data).to.be.a('number') expect(data).to.be.a('number');
expect(data).to.be.equal(1); expect(data).to.be.equal(1);
done() done();
}) });
}); });
it('countryDistinct', function (done) { it('countryDistinct', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -662,19 +670,19 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryDistinct(column_name: "last_update") { last_update } } ` query: `{ countryDistinct(column_name: "last_update") { last_update } } `
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const data = res.body.data.countryDistinct; const data = res.body.data.countryDistinct;
expect(data).to.be.a('array') expect(data).to.be.a('array');
expect(data[0]).to.be.a('object') expect(data[0]).to.be.a('object');
expect(data[0]).to.have.all.keys(['last_update']) expect(data[0]).to.have.all.keys(['last_update']);
expect(data[0].last_update).to.be.match(/\d+/); expect(data[0].last_update).to.be.match(/\d+/);
done() done();
}) });
}); });
if (dbConfig.client !== 'mssql') { if (dbConfig.client !== 'mssql') {
it('countryGroupBy', function (done) { it('countryGroupBy', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -682,18 +690,18 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryGroupBy(fields: "last_update",limit:5) { last_update count } } ` query: `{ countryGroupBy(fields: "last_update",limit:5) { last_update count } } `
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const data = res.body.data.countryGroupBy; const data = res.body.data.countryGroupBy;
expect(data.length).to.be.most(5); expect(data.length).to.be.most(5);
expect(data[0].count).to.be.greaterThan(0); expect(data[0].count).to.be.greaterThan(0);
expect(data[0].last_update).to.be.a('string'); expect(data[0].last_update).to.be.a('string');
expect(Object.keys(data[0]).length).to.be.equal(2); expect(Object.keys(data[0]).length).to.be.equal(2);
done() done();
}) });
}); });
it('countryGroupBy - Multiple', function (done) { it('countryGroupBy - Multiple', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -701,7 +709,7 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryGroupBy(fields: "last_update,country",limit:5) { last_update country count } } ` query: `{ countryGroupBy(fields: "last_update,country",limit:5) { last_update country count } } `
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const data = res.body.data.countryGroupBy; const data = res.body.data.countryGroupBy;
expect(data.length).to.be.most(5); expect(data.length).to.be.most(5);
@ -709,11 +717,11 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
expect(data[0].last_update).to.be.a('string'); expect(data[0].last_update).to.be.a('string');
expect(data[0].country).to.be.a('string'); expect(data[0].country).to.be.a('string');
expect(Object.keys(data[0]).length).to.be.equal(3); expect(Object.keys(data[0]).length).to.be.equal(3);
done() done();
}) });
}); });
it('countryAggregate', function (done) { it('countryAggregate', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -721,7 +729,7 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryAggregate(func: "sum,avg,min,max,count", column_name : "country_id") { sum avg min max count } } ` query: `{ countryAggregate(func: "sum,avg,min,max,count", column_name : "country_id") { sum avg min max count } } `
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const data = res.body.data.countryAggregate; const data = res.body.data.countryAggregate;
expect(data).to.be.a('array'); expect(data).to.be.a('array');
@ -730,14 +738,19 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
expect(data[0].max).to.be.a('number'); expect(data[0].max).to.be.a('number');
expect(data[0].avg).to.be.a('number'); expect(data[0].avg).to.be.a('number');
expect(data[0].sum).to.be.a('number'); expect(data[0].sum).to.be.a('number');
expect(data[0].count).to.be.a('number').and.satisfy(num => num === parseInt(num), 'count should be an integer'); expect(data[0].count)
.to.be.a('number')
.and.satisfy(
num => num === parseInt(num),
'count should be an integer'
);
expect(Object.keys(data[0]).length).to.be.equal(5); expect(Object.keys(data[0]).length).to.be.equal(5);
} }
done(); done();
}) });
}); });
it('countryDistribution', function (done) { it('countryDistribution', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -745,67 +758,71 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `{ countryDistribution(column_name : "country_id") { range count } } ` query: `{ countryDistribution(column_name : "country_id") { range count } } `
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const data = res.body.data.countryDistribution; const data = res.body.data.countryDistribution;
expect(data).to.be.a('array'); expect(data).to.be.a('array');
expect(data[0].count).to.be.a('number'); expect(data[0].count).to.be.a('number');
expect(data[0].count).satisfies(num => num === parseInt(num) && num >= 0, 'should be a positive integer'); expect(data[0].count).satisfies(
num => num === parseInt(num) && num >= 0,
'should be a positive integer'
);
expect(data[0].range).to.be.a('string'); expect(data[0].range).to.be.a('string');
expect(data[0].range).to.be.match(/^\d+-\d+$/, 'should match {num start}-{num end} format') expect(data[0].range).to.be.match(
/^\d+-\d+$/,
'should match {num start}-{num end} format'
);
done(); done();
}) });
}); });
} }
/**** Query : END ****/ /**** Query : END ****/
/**** Mutation : START ****/ /**** Mutation : START ****/
describe('Mutation', function () { describe('Mutation', function() {
const COUNTRY_ID = 9999; const COUNTRY_ID = 9999;
// const COUNTRY_CREATE_ID = 9998; // const COUNTRY_CREATE_ID = 9998;
// const COUNTRY_NAME = 'test-name'; // const COUNTRY_NAME = 'test-name';
before(function(done) {
before(function (done) {
// create table entry for update and delete // create table entry for update and delete
// let db = knex(config.envs.dev.db[0])('country'); // let db = knex(config.envs.dev.db[0])('country');
// db.insert({ // db.insert({
// country_id: COUNTRY_ID, // country_id: COUNTRY_ID,
// country: COUNTRY_NAME // country: COUNTRY_NAME
// }).finally(() => done()) // }).finally(() => done())
done() done();
}) });
after(function (done) {
after(function(done) {
// delete table entries which is created for the test // delete table entries which is created for the test
// let db = knex(config.envs.dev.db[0])('country'); // let db = knex(config.envs.dev.db[0])('country');
// db.whereIn('country_id', [COUNTRY_ID, COUNTRY_CREATE_ID]) // db.whereIn('country_id', [COUNTRY_ID, COUNTRY_CREATE_ID])
// .del() // .del()
// .finally(() => done()) // .finally(() => done())
done() done();
}) });
it('countryCreate', function (done) { it('countryCreate', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
.set('xc-auth', token) .set('xc-auth', token)
.send({ .send({
query: `mutation{ countryCreate( data : { country: "abcd" ${dbConfig.client === 'sqlite3' ? ' country_id : 999 ' : ''} }) { country_id country } } ` query: `mutation{ countryCreate( data : { country: "abcd" ${
dbConfig.client === 'sqlite3' ? ' country_id : 999 ' : ''
} }) { country_id country } } `
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const data = res.body.data.countryCreate; const data = res.body.data.countryCreate;
expect(data).to.be.a('object'); expect(data).to.be.a('object');
expect(data.country_id).to.be.a('number'); expect(data.country_id).to.be.a('number');
expect(data.country).to.be.equal('abcd'); expect(data.country).to.be.equal('abcd');
done(); done();
}) });
}); });
it('countryUpdate', function(done) {
it('countryUpdate', function (done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -813,16 +830,16 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `mutation{ countryUpdate( id : "${COUNTRY_ID}", data : { country: "abcd" }){ country } } ` query: `mutation{ countryUpdate( id : "${COUNTRY_ID}", data : { country: "abcd" }){ country } } `
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const data = res.body.data.countryUpdate; const data = res.body.data.countryUpdate;
expect(data).to.be.a('object'); expect(data).to.be.a('object');
// todo: // todo:
done(); done();
}) });
}); });
it('countryDelete', function (done) { it('countryDelete', function(done) {
request(app) request(app)
.post(`/nc/${projectId}/v1/graphql`) .post(`/nc/${projectId}/v1/graphql`)
@ -830,16 +847,15 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
.send({ .send({
query: `mutation{ countryDelete( id : "${COUNTRY_ID}") } ` query: `mutation{ countryDelete( id : "${COUNTRY_ID}") } `
}) })
.expect(200, function (err, res) { .expect(200, function(err, res) {
if (err) done(err); if (err) done(err);
const data = res.body.data.countryDelete; const data = res.body.data.countryDelete;
expect(data).to.be.a('number') expect(data).to.be.a('number');
// todo: // todo:
done(); done();
}) });
}); });
}) });
/**** Mutation : END ****/ /**** Mutation : END ****/
// countryCreateBulk(data: [countryInput]): [Int] // countryCreateBulk(data: [countryInput]): [Int]
@ -849,7 +865,8 @@ describe('{Auth, CRUD, HasMany, Belongs} Tests', () => {
}); });
/**** country : END ****/ /**** country : END ****/
}); });
});/** });
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>

37
packages/nocodb/src/__tests__/noco/NcConfigFactory.test.ts

@ -1,10 +1,8 @@
import {expect} from 'chai'; import { expect } from 'chai';
import 'mocha'; import 'mocha';
import {NcConfigFactory} from "../../lib"; import { NcConfigFactory } from '../../lib';
describe('Config Factory Tests', () => { describe('Config Factory Tests', () => {
const expectedObject = { const expectedObject = {
client: 'pg', client: 'pg',
connection: { connection: {
@ -24,31 +22,36 @@ describe('Config Factory Tests', () => {
acquireConnectionTimeout: 600000 acquireConnectionTimeout: 600000
}; };
before(function (done) { before(function(done) {
done(); done();
}); });
after(done => {
after((done) => {
done(); done();
}) });
it('Generate config from string', function (done) { it('Generate config from string', function(done) {
const config = NcConfigFactory.metaUrlToDbConfig(`pg://localhost:5432?u=postgres&p=xgene&d=abcde`); const config = NcConfigFactory.metaUrlToDbConfig(
`pg://localhost:5432?u=postgres&p=xgene&d=abcde`
);
const { pool, ssl, ...rest } = expectedObject; const { pool, ssl, ...rest } = expectedObject;
expect(config).to.deep.equal(rest); expect(config).to.deep.equal(rest);
done(); done();
}); });
it('Connection string with nested property', function (done) { it('Connection string with nested property', function(done) {
const config = NcConfigFactory.metaUrlToDbConfig(`pg://localhost:5432?u=postgres&p=xgene&d=abcde&pool.min=1&pool.max=2&ssl.rejectUnauthorized=false`); const config = NcConfigFactory.metaUrlToDbConfig(
`pg://localhost:5432?u=postgres&p=xgene&d=abcde&pool.min=1&pool.max=2&ssl.rejectUnauthorized=false`
);
expect(config).to.deep.equal(expectedObject); expect(config).to.deep.equal(expectedObject);
done(); done();
}); });
it('Allow creating config from JSON string', function(done) { it('Allow creating config from JSON string', function(done) {
try { try {
process.env.NC_DB_JSON = JSON.stringify(expectedObject); process.env.NC_DB_JSON = JSON.stringify(expectedObject);
const { meta: { db: config } } = NcConfigFactory.make(); const {
meta: { db: config }
} = NcConfigFactory.make();
expect(config).to.deep.equal(expectedObject); expect(config).to.deep.equal(expectedObject);
done(); done();
} finally { } finally {
@ -58,8 +61,10 @@ describe('Config Factory Tests', () => {
it('Allow creating config from JSON file', function(done) { it('Allow creating config from JSON file', function(done) {
try { try {
process.env.NC_DB_JSON_FILE = `${__dirname}/dbConfig.json`; process.env.NC_DB_JSON_FILE = `${__dirname}/dbConfig.json`;
const { meta: { db: config } } = NcConfigFactory.make(); const {
meta: { db: config }
} = NcConfigFactory.make();
expect(config).to.deep.equal(expectedObject); expect(config).to.deep.equal(expectedObject);
done(); done();
} finally { } finally {

789
packages/nocodb/src/__tests__/rest.test.ts

File diff suppressed because it is too large Load Diff

16
packages/nocodb/src/example/docker.ts

@ -1,13 +1,15 @@
import cors from 'cors'; import cors from 'cors';
import express from 'express'; import express from 'express';
import Noco from "../lib/noco/Noco"; import Noco from '../lib/noco/Noco';
process.env.NC_VERSION = '0009044'; process.env.NC_VERSION = '0009044';
const server = express(); const server = express();
server.use(cors({ server.use(
exposedHeaders: 'xc-db-response' cors({
})); exposedHeaders: 'xc-db-response'
})
);
server.set('view engine', 'ejs'); server.set('view engine', 'ejs');
@ -17,16 +19,14 @@ server.set('view engine', 'ejs');
// process.env[`NC_TRY`] = 'true'; // process.env[`NC_TRY`] = 'true';
// process.env[`NC_DASHBOARD_URL`] = '/test'; // process.env[`NC_DASHBOARD_URL`] = '/test';
process.env[`DEBUG`] = 'xc*'; process.env[`DEBUG`] = 'xc*';
(async () => { (async () => {
server.use(await Noco.init({})); server.use(await Noco.init({}));
server.listen(process.env.PORT || 8080, () => { server.listen(process.env.PORT || 8080, () => {
console.log(`App started successfully.\nVisit -> ${Noco.dashboardUrl}`); console.log(`App started successfully.\nVisit -> ${Noco.dashboardUrl}`);
}) });
})().catch(e => console.log(e)) })().catch(e => console.log(e));
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd

38
packages/nocodb/src/example/try.ts

@ -1,38 +1,42 @@
import cors from 'cors'; import cors from 'cors';
import express from 'express'; import express from 'express';
import {NcConfigFactory, Noco} from "../lib"; import { NcConfigFactory, Noco } from '../lib';
process.env.DATABASE_URL = 'mysql://root:password@localhost:3306/sakila'
process.env.DATABASE_URL = 'mysql://root:password@localhost:3306/sakila';
const url = NcConfigFactory.extractXcUrlFromJdbc(process.env.DATABASE_URL); const url = NcConfigFactory.extractXcUrlFromJdbc(process.env.DATABASE_URL);
process.env.NC_DB = url; process.env.NC_DB = url;
(async () => { (async () => {
const server = express(); const server = express();
server.use(cors()); server.use(cors());
server.set('view engine', 'ejs'); server.set('view engine', 'ejs');
const app = new Noco(); const app = new Noco();
server.use(await app.init({ server.use(
await app.init({
async afterMetaMigrationInit(): Promise<void> { async afterMetaMigrationInit(): Promise<void> {
if (!(await app.ncMeta.projectList())?.length) { if (!(await app.ncMeta.projectList())?.length) {
const config = NcConfigFactory.makeProjectConfigFromUrl(url); const config = NcConfigFactory.makeProjectConfigFromUrl(url);
const project = await app.ncMeta.projectCreate(config.title, config, ''); const project = await app.ncMeta.projectCreate(
config.title,
config,
''
);
await app.ncMeta.projectStatusUpdate(config.title, 'started'); await app.ncMeta.projectStatusUpdate(config.title, 'started');
await app.ncMeta.projectAddUser(project.id, 1, 'owner,creator'); await app.ncMeta.projectAddUser(project.id, 1, 'owner,creator');
} }
} }
}));
server.listen(process.env.PORT || 8080, () => {
console.log(`App started successfully.\nVisit -> http://localhost:${process.env.PORT || 8080}/xc`);
}) })
} );
)().catch(e => console.log(e)) server.listen(process.env.PORT || 8080, () => {
console.log(
`App started successfully.\nVisit -> http://localhost:${process.env
.PORT || 8080}/xc`
);
});
})().catch(e => console.log(e));
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd

19
packages/nocodb/src/interface/IEmailAdapter.ts

@ -1,21 +1,18 @@
export default interface IEmailAdapter { export default interface IEmailAdapter {
init(): Promise<any> init(): Promise<any>;
mailSend(mail:XcEmail): Promise<any> mailSend(mail: XcEmail): Promise<any>;
test(email): Promise<boolean> test(email): Promise<boolean>;
} }
interface XcEmail { interface XcEmail {
// from?:string; // from?:string;
to:string; to: string;
subject:string; subject: string;
html?:string; html?: string;
text?:string; text?: string;
} }
export { export { XcEmail };
XcEmail
}
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd

15
packages/nocodb/src/interface/IStorageAdapter.ts

@ -1,12 +1,11 @@
export default interface IStorageAdapter { export default interface IStorageAdapter {
init(): Promise<any> init(): Promise<any>;
fileCreate(destPath: string, file: XcFile): Promise<any> fileCreate(destPath: string, file: XcFile): Promise<any>;
fileDelete(filePath: string): Promise<any> fileDelete(filePath: string): Promise<any>;
fileRead(filePath: string): Promise<any> fileRead(filePath: string): Promise<any>;
test(): Promise<boolean> test(): Promise<boolean>;
} }
interface XcFile { interface XcFile {
originalname: string; originalname: string;
path: string; path: string;
@ -14,9 +13,7 @@ interface XcFile {
size: number | string; size: number | string;
} }
export { export { XcFile };
XcFile
}
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd

5
packages/nocodb/src/interface/XcDynamicChanges.ts

@ -1,13 +1,12 @@
export default interface XcDynamicChanges { export default interface XcDynamicChanges {
onTableCreate(tn: string): Promise<void>; onTableCreate(tn: string): Promise<void>;
onTableUpdate(changeObj:any):Promise<void>; onTableUpdate(changeObj: any): Promise<void>;
onTableDelete(tn: string): Promise<void>; onTableDelete(tn: string): Promise<void>;
onTableRename(oldTableName: string, newTableName: string): Promise<void>; onTableRename(oldTableName: string, newTableName: string): Promise<void>;
onHandlerCodeUpdate(tn: string): Promise<void>; onHandlerCodeUpdate(tn: string): Promise<void>;
onMetaUpdate(tn:string):Promise<void>; onMetaUpdate(tn: string): Promise<void>;
} }
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *

6
packages/nocodb/src/interface/XcMetaMgr.ts

@ -1,9 +1,5 @@
// eslint-disable-next-line @typescript-eslint/no-empty-interface // eslint-disable-next-line @typescript-eslint/no-empty-interface
export default interface XcMetaMgr { export default interface XcMetaMgr {}
}
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *

163
packages/nocodb/src/interface/config.ts

@ -1,6 +1,6 @@
import {Handler} from "express"; import { Handler } from 'express';
import * as e from 'express'; import * as e from 'express';
import Knex from "knex"; import Knex from 'knex';
export interface Route { export interface Route {
path: string; path: string;
@ -13,7 +13,6 @@ export interface Route {
functions?: string[]; functions?: string[];
} }
export enum RouteType { export enum RouteType {
GET = 'get', GET = 'get',
POST = 'post', POST = 'post',
@ -24,30 +23,30 @@ export enum RouteType {
OPTIONS = 'options' OPTIONS = 'options'
} }
type InflectionTypes = 'pluralize' | type InflectionTypes =
'singularize' | | 'pluralize'
'inflect' | | 'singularize'
'camelize' | | 'inflect'
'underscore' | | 'camelize'
'humanize' | | 'underscore'
'capitalize' | | 'humanize'
'dasherize' | | 'capitalize'
'titleize' | | 'dasherize'
'demodulize' | | 'titleize'
'tableize' | | 'demodulize'
'classify' | | 'tableize'
'foreign_key' | | 'classify'
'ordinalize' | | 'foreign_key'
'transform' | 'none' ; | 'ordinalize'
| 'transform'
| 'none';
export interface DbConfig extends Knex.Config { export interface DbConfig extends Knex.Config {
client: string; client: string;
connection: Knex.StaticConnectionConfig | Knex.Config | any; connection: Knex.StaticConnectionConfig | Knex.Config | any;
meta: { meta: {
dbAlias: string; dbAlias: string;
metaTables?: 'db' | 'file'; metaTables?: 'db' | 'file';
@ -73,7 +72,7 @@ export interface DbConfig extends Knex.Config {
type: 'rest' | 'graphql' | 'grpc'; type: 'rest' | 'graphql' | 'grpc';
prefix: string; prefix: string;
swagger?: boolean; swagger?: boolean;
graphiql?: boolean graphiql?: boolean;
graphqlDepthLimit?: number; graphqlDepthLimit?: number;
}; };
@ -86,15 +85,14 @@ export interface DbConfig extends Knex.Config {
print?: boolean; print?: boolean;
explain?: boolean; explain?: boolean;
measure?: boolean; measure?: boolean;
}, };
reset?: boolean; reset?: boolean;
dbtype?: "vitess" | string; dbtype?: 'vitess' | string;
pluralize?: boolean; pluralize?: boolean;
inflection?: { inflection?: {
tn?: InflectionTypes; tn?: InflectionTypes;
cn?: InflectionTypes; cn?: InflectionTypes;
} };
}; };
} }
@ -117,19 +115,19 @@ export interface AuthConfig {
secret: string; secret: string;
[key: string]: any; [key: string]: any;
dbAlias?: string; dbAlias?: string;
options?: JwtOptions options?: JwtOptions;
}, };
masterKey?: { masterKey?: {
secret: string secret: string;
}; };
middleware?: { middleware?: {
url: string; url: string;
}, };
disabled?: boolean; disabled?: boolean;
} }
export interface MiddlewareConfig { export interface MiddlewareConfig {
handler?: (...args: any[]) => any handler?: (...args: any[]) => any;
} }
export interface ACLConfig { export interface ACLConfig {
@ -138,31 +136,30 @@ export interface ACLConfig {
} }
export interface MailerConfig { export interface MailerConfig {
[key: string]: any [key: string]: any;
} }
export interface ServerlessConfig { export interface ServerlessConfig {
aws?: { aws?: {
lambda: boolean lambda: boolean;
}; };
gcp?: { gcp?: {
cloudFunction: boolean cloudFunction: boolean;
}; };
azure?: { azure?: {
cloudFunctionApp: boolean cloudFunctionApp: boolean;
}; };
zeit?: { zeit?: {
now: boolean now: boolean;
}; };
alibaba?: { alibaba?: {
functionCompute: boolean functionCompute: boolean;
}; };
serverlessFramework?: { serverlessFramework?: {
http: boolean http: boolean;
}; };
} }
export interface NcGui { export interface NcGui {
path?: string; path?: string;
disabled?: boolean; disabled?: boolean;
@ -177,12 +174,11 @@ export interface NcConfig {
envs: { envs: {
[key: string]: { [key: string]: {
db: DbConfig[], db: DbConfig[];
api?: any, api?: any;
publicUrl?: string; publicUrl?: string;
} };
} };
// dbs: DbConfig[]; // dbs: DbConfig[];
@ -197,20 +193,19 @@ export interface NcConfig {
make?: () => NcConfig; make?: () => NcConfig;
serverless?: ServerlessConfig; serverless?: ServerlessConfig;
toolDir?: string; toolDir?: string;
env?: 'production' | 'dev' | 'test' | string, env?: 'production' | 'dev' | 'test' | string;
workingEnv?: string, workingEnv?: string;
seedsFolder?: string | string[], seedsFolder?: string | string[];
queriesFolder?: string | string[], queriesFolder?: string | string[];
apisFolder?: string | string[], apisFolder?: string | string[];
projectType?: "rest" | "graphql" | "grpc", projectType?: 'rest' | 'graphql' | 'grpc';
type?: "mvc" | "package" | "docker", type?: 'mvc' | 'package' | 'docker';
language?: "ts" | "js", language?: 'ts' | 'js';
meta?: { meta?: {
db?: any db?: any;
}, };
api?: any; api?: any;
gui?: NcGui; gui?: NcGui;
try?: boolean; try?: boolean;
@ -219,51 +214,52 @@ export interface NcConfig {
prefix?: string; prefix?: string;
publicUrl?: string; publicUrl?: string;
} }
export interface Event { export interface Event {
title: string; title: string;
tn: string; tn: string;
url url;
headers headers;
operation operation;
event event;
retry retry;
max max;
interval interval;
timeout timeout;
} }
export interface Acl { export interface Acl {
[role: string]: { [role: string]:
create: boolean | ColumnAcl | {
[key: string]: boolean | ColumnAcl create: boolean | ColumnAcl;
} | boolean |any [key: string]: boolean | ColumnAcl;
}
| boolean
| any;
} }
export interface ColumnAcl { export interface ColumnAcl {
columns: { columns: {
[cn: string]: boolean [cn: string]: boolean;
}, };
assign?: { assign?: {
[cn: string]: any [cn: string]: any;
} };
} }
export interface Acls { export interface Acls {
[tn: string]: Acl [tn: string]: Acl;
} }
export enum ServerlessType { export enum ServerlessType {
AWS_LAMBDA = "AWS_LAMBDA", AWS_LAMBDA = 'AWS_LAMBDA',
GCP_FUNCTION = "GCP_FUNCTION", GCP_FUNCTION = 'GCP_FUNCTION',
AZURE_FUNCTION_APP = "AZURE_FUNCTION_APP", AZURE_FUNCTION_APP = 'AZURE_FUNCTION_APP',
ALIYUN = "ALIYUN", ALIYUN = 'ALIYUN',
ZEIT = "ZEIT", ZEIT = 'ZEIT',
LYRID = "LYRID", LYRID = 'LYRID',
SERVERLESS = "SERVERLESS" SERVERLESS = 'SERVERLESS'
} }
export class Result { export class Result {
@ -276,12 +272,8 @@ export class Result {
this.message = message; this.message = message;
this.data = data; this.data = data;
} }
} }
enum HTTPType { enum HTTPType {
GET = 'get', GET = 'get',
POST = 'post', POST = 'post',
@ -290,7 +282,6 @@ enum HTTPType {
PATCH = 'patch', PATCH = 'patch',
HEAD = 'head', HEAD = 'head',
OPTIONS = 'options' OPTIONS = 'options'
} }
export interface XcRoute { export interface XcRoute {

8
packages/nocodb/src/lib/dataMapper/index.ts

@ -1,9 +1,9 @@
export {DbFactory} from './lib/DbFactory'; export { DbFactory } from './lib/DbFactory';
export {BaseModelSql} from './lib/sql/BaseModelSql'; export { BaseModelSql } from './lib/sql/BaseModelSql';
import XKnex, {Knex} from './lib/sql/CustomKnex'; import XKnex, { Knex } from './lib/sql/CustomKnex';
export {XKnex, Knex}; export { XKnex, Knex };
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *

670
packages/nocodb/src/lib/dataMapper/lib/BaseModel.ts

File diff suppressed because it is too large Load Diff

12
packages/nocodb/src/lib/dataMapper/lib/DbFactory.ts

@ -2,12 +2,14 @@ import knex from './sql/CustomKnex';
export class DbFactory { export class DbFactory {
static create(connectionConfig) { static create(connectionConfig) {
if (connectionConfig.client === "sqlite3") { if (connectionConfig.client === 'sqlite3') {
return knex(connectionConfig.connection) return knex(connectionConfig.connection);
} else if (['mysql', 'mysql2', 'pg', 'mssql'].includes(connectionConfig.client)) { } else if (
return knex(connectionConfig) ['mysql', 'mysql2', 'pg', 'mssql'].includes(connectionConfig.client)
) {
return knex(connectionConfig);
} }
throw new Error("Database not supported"); throw new Error('Database not supported');
} }
} }
/** /**

794
packages/nocodb/src/lib/dataMapper/lib/sql/CustomKnex.ts

File diff suppressed because it is too large Load Diff

103
packages/nocodb/src/lib/dataMapper/lib/sql/formulaQueryBuilderFromString.ts

@ -1,30 +1,37 @@
import jsep from 'jsep'; import jsep from 'jsep';
import mapFunctionName from "./mapFunctionName"; import mapFunctionName from './mapFunctionName';
// todo: switch function based on database // todo: switch function based on database
export function formulaQueryBuilderFromString(str, alias, knex) { export function formulaQueryBuilderFromString(str, alias, knex) {
return formulaQueryBuilder(jsep(str), alias, knex) return formulaQueryBuilder(jsep(str), alias, knex);
} }
export default function formulaQueryBuilder(
export default function formulaQueryBuilder(tree, alias, knex, aliasToColumn = {}) { tree,
const fn = (pt, a?, prevBinaryOp ?) => { alias,
knex,
aliasToColumn = {}
) {
const fn = (pt, a?, prevBinaryOp?) => {
const colAlias = a ? ` as ${a}` : ''; const colAlias = a ? ` as ${a}` : '';
if (pt.type === 'CallExpression') { if (pt.type === 'CallExpression') {
switch (pt.callee.name) { switch (pt.callee.name) {
case 'ADD': case 'ADD':
case 'SUM': case 'SUM':
if (pt.arguments.length > 1) { if (pt.arguments.length > 1) {
return fn({ return fn(
type: 'BinaryExpression', {
operator: '+', type: 'BinaryExpression',
left: pt.arguments[0], operator: '+',
right: {...pt, arguments: pt.arguments.slice(1)} left: pt.arguments[0],
}, a, prevBinaryOp) right: { ...pt, arguments: pt.arguments.slice(1) }
},
a,
prevBinaryOp
);
} else { } else {
return fn(pt.arguments[0], a, prevBinaryOp) return fn(pt.arguments[0], a, prevBinaryOp);
} }
break; break;
// case 'AVG': // case 'AVG':
@ -42,54 +49,72 @@ export default function formulaQueryBuilder(tree, alias, knex, aliasToColumn = {
case 'CONCAT': case 'CONCAT':
if (knex.clientType() === 'sqlite3') { if (knex.clientType() === 'sqlite3') {
if (pt.arguments.length > 1) { if (pt.arguments.length > 1) {
return fn({ return fn(
type: 'BinaryExpression', {
operator: '||', type: 'BinaryExpression',
left: pt.arguments[0], operator: '||',
right: {...pt, arguments: pt.arguments.slice(1)} left: pt.arguments[0],
}, a, prevBinaryOp) right: { ...pt, arguments: pt.arguments.slice(1) }
},
a,
prevBinaryOp
);
} else { } else {
return fn(pt.arguments[0], a, prevBinaryOp) return fn(pt.arguments[0], a, prevBinaryOp);
} }
} }
break; break;
default: { default:
const res = mapFunctionName({pt, knex, alias, a, aliasToCol: aliasToColumn, fn, colAlias, prevBinaryOp}) {
if (res) return res; const res = mapFunctionName({
} pt,
break knex,
alias,
a,
aliasToCol: aliasToColumn,
fn,
colAlias,
prevBinaryOp
});
if (res) return res;
}
break;
} }
return knex.raw(`${pt.callee.name}(${pt.arguments.map(arg => fn(arg).toQuery()).join()})${colAlias}`) return knex.raw(
`${pt.callee.name}(${pt.arguments
.map(arg => fn(arg).toQuery())
.join()})${colAlias}`
);
} else if (pt.type === 'Literal') { } else if (pt.type === 'Literal') {
return knex.raw(`?${colAlias}`, [pt.value]); return knex.raw(`?${colAlias}`, [pt.value]);
} else if (pt.type === 'Identifier') { } else if (pt.type === 'Identifier') {
return knex.raw(`??${colAlias}`, [aliasToColumn[pt.name] || pt.name]); return knex.raw(`??${colAlias}`, [aliasToColumn[pt.name] || pt.name]);
} else if (pt.type === 'BinaryExpression') { } else if (pt.type === 'BinaryExpression') {
if (pt.operator === '==') { if (pt.operator === '==') {
pt.operator = '=' pt.operator = '=';
} }
if (pt.operator === '/') { if (pt.operator === '/') {
pt.left = { pt.left = {
callee: {name: 'FLOAT'}, callee: { name: 'FLOAT' },
type: 'CallExpression', type: 'CallExpression',
arguments: [ arguments: [pt.left]
pt.left };
]
}
} }
const query = knex.raw(
const query = knex.raw(`${fn(pt.left, null, pt.operator).toQuery()} ${pt.operator} ${fn(pt.right, null, pt.operator).toQuery()}${colAlias}`) `${fn(pt.left, null, pt.operator).toQuery()} ${pt.operator} ${fn(
pt.right,
null,
pt.operator
).toQuery()}${colAlias}`
);
if (prevBinaryOp && pt.operator !== prevBinaryOp) { if (prevBinaryOp && pt.operator !== prevBinaryOp) {
query.wrap('(', ')') query.wrap('(', ')');
} }
return query; return query;
} }
}; };
return fn(tree, alias) return fn(tree, alias);
} }

80
packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/commonFns.ts

@ -1,49 +1,87 @@
import {MapFnArgs} from "../mapFunctionName"; import { MapFnArgs } from '../mapFunctionName';
export default { export default {
// todo: handle default case // todo: handle default case
SWITCH: (args: MapFnArgs) => { SWITCH: (args: MapFnArgs) => {
const count = Math.floor((args.pt.arguments.length - 1) / 2) const count = Math.floor((args.pt.arguments.length - 1) / 2);
let query = ''; let query = '';
const switchVal = args.fn(args.pt.arguments[0]).toQuery(); const switchVal = args.fn(args.pt.arguments[0]).toQuery();
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
query += args.knex.raw(`\n\tWHEN ${args.fn(args.pt.arguments[i * 2 + 1]).toQuery()} THEN ${args.fn(args.pt.arguments[i * 2 + 2]).toQuery()}`).toQuery() query += args.knex
.raw(
`\n\tWHEN ${args
.fn(args.pt.arguments[i * 2 + 1])
.toQuery()} THEN ${args.fn(args.pt.arguments[i * 2 + 2]).toQuery()}`
)
.toQuery();
} }
if (args.pt.arguments.length % 2 === 0) { if (args.pt.arguments.length % 2 === 0) {
query += args.knex.raw(`\n\tELSE ${args.fn(args.pt.arguments[args.pt.arguments.length - 1]).toQuery()}`).toQuery() query += args.knex
.raw(
`\n\tELSE ${args
.fn(args.pt.arguments[args.pt.arguments.length - 1])
.toQuery()}`
)
.toQuery();
} }
return args.knex.raw(`CASE ${switchVal} ${query}\n END${args.colAlias}`) return args.knex.raw(`CASE ${switchVal} ${query}\n END${args.colAlias}`);
}, },
IF: (args: MapFnArgs) => { IF: (args: MapFnArgs) => {
let query = args.knex.raw(`\n\tWHEN ${args.fn(args.pt.arguments[0]).toQuery()} THEN ${args.fn(args.pt.arguments[1]).toQuery()}`).toQuery(); let query = args.knex
.raw(
`\n\tWHEN ${args.fn(args.pt.arguments[0]).toQuery()} THEN ${args
.fn(args.pt.arguments[1])
.toQuery()}`
)
.toQuery();
if (args.pt.arguments[2]) { if (args.pt.arguments[2]) {
query += args.knex.raw(`\n\tELSE ${args.fn(args.pt.arguments[2]).toQuery()}`).toQuery() query += args.knex
.raw(`\n\tELSE ${args.fn(args.pt.arguments[2]).toQuery()}`)
.toQuery();
} }
return args.knex.raw(`CASE ${query}\n END${args.colAlias}`) return args.knex.raw(`CASE ${query}\n END${args.colAlias}`);
}, },
TRUE: (_args) => 1, TRUE: _args => 1,
FALSE: (_args) => 0, FALSE: _args => 0,
AND: (args: MapFnArgs) => { AND: (args: MapFnArgs) => {
return args.knex.raw(`${args.knex.raw(`${args.pt.arguments.map(ar => args.fn(ar).toQuery()).join(' AND ')}`).wrap('(', ')').toQuery()}${args.colAlias}`) return args.knex.raw(
`${args.knex
.raw(
`${args.pt.arguments.map(ar => args.fn(ar).toQuery()).join(' AND ')}`
)
.wrap('(', ')')
.toQuery()}${args.colAlias}`
);
}, },
OR: (args: MapFnArgs) => { OR: (args: MapFnArgs) => {
return args.knex.raw(`${args.knex.raw(`${args.pt.arguments.map(ar => args.fn(ar).toQuery()).join(' OR ')}`).wrap('(', ')').toQuery()}${args.colAlias}`) return args.knex.raw(
`${args.knex
.raw(
`${args.pt.arguments.map(ar => args.fn(ar).toQuery()).join(' OR ')}`
)
.wrap('(', ')')
.toQuery()}${args.colAlias}`
);
}, },
AVG: (args: MapFnArgs) => { AVG: (args: MapFnArgs) => {
if (args.pt.arguments.length > 1) { if (args.pt.arguments.length > 1) {
return args.fn({ return args.fn(
type: 'BinaryExpression', {
operator: '/', type: 'BinaryExpression',
left: {...args.pt, callee: {name: 'SUM'}}, operator: '/',
right: {type: 'Literal', value: args.pt.arguments.length} left: { ...args.pt, callee: { name: 'SUM' } },
}, args.a, args.prevBinaryOp) right: { type: 'Literal', value: args.pt.arguments.length }
},
args.a,
args.prevBinaryOp
);
} else { } else {
return args.fn(args.pt.arguments[0], args.a, args.prevBinaryOp) return args.fn(args.pt.arguments[0], args.a, args.prevBinaryOp);
} }
}, },
FLOAT: (args: MapFnArgs) => { FLOAT: (args: MapFnArgs) => {
return args.fn(args.pt?.arguments?.[0]).wrap('(',')'); return args.fn(args.pt?.arguments?.[0]).wrap('(', ')');
} }
} };

63
packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/mssql.ts

@ -1,59 +1,86 @@
import {MapFnArgs} from "../mapFunctionName"; import { MapFnArgs } from '../mapFunctionName';
import commonFns from "./commonFns"; import commonFns from './commonFns';
const mssql = { const mssql = {
...commonFns, ...commonFns,
MIN: (args: MapFnArgs) => { MIN: (args: MapFnArgs) => {
if (args.pt.arguments.length === 1) { if (args.pt.arguments.length === 1) {
return args.fn(args.pt.arguments[0]) return args.fn(args.pt.arguments[0]);
} }
let query = ''; let query = '';
for (const [i, arg] of Object.entries(args.pt.arguments)) { for (const [i, arg] of Object.entries(args.pt.arguments)) {
if (+i === args.pt.arguments.length - 1) { if (+i === args.pt.arguments.length - 1) {
query += args.knex.raw(`\n\tElse ${args.fn(arg).toQuery()}`).toQuery() query += args.knex.raw(`\n\tElse ${args.fn(arg).toQuery()}`).toQuery();
} else { } else {
query += args.knex.raw(`\n\tWhen ${args.pt.arguments.filter((_, j) => +i !== j).map(arg1 => `${args.fn(arg).toQuery()} < ${args.fn(arg1).toQuery()}`).join(' And ')} Then ${args.fn(arg).toQuery()}`).toQuery() query += args.knex
.raw(
`\n\tWhen ${args.pt.arguments
.filter((_, j) => +i !== j)
.map(
arg1 => `${args.fn(arg).toQuery()} < ${args.fn(arg1).toQuery()}`
)
.join(' And ')} Then ${args.fn(arg).toQuery()}`
)
.toQuery();
} }
} }
return args.knex.raw(`Case ${query}\n End${args.colAlias}`) return args.knex.raw(`Case ${query}\n End${args.colAlias}`);
}, },
MAX: (args: MapFnArgs) => { MAX: (args: MapFnArgs) => {
if (args.pt.arguments.length === 1) { if (args.pt.arguments.length === 1) {
return args.fn(args.pt.arguments[0]) return args.fn(args.pt.arguments[0]);
} }
let query = ''; let query = '';
for (const [i, arg] of Object.entries(args.pt.arguments)) { for (const [i, arg] of Object.entries(args.pt.arguments)) {
if (+i === args.pt.arguments.length - 1) { if (+i === args.pt.arguments.length - 1) {
query += args.knex.raw(`\nElse ${args.fn(arg).toQuery()}`).toQuery() query += args.knex.raw(`\nElse ${args.fn(arg).toQuery()}`).toQuery();
} else { } else {
query += args.knex.raw(`\nWhen ${args.pt.arguments.filter((_, j) => +i !== j).map(arg1 => `${args.fn(arg).toQuery()} > ${args.fn(arg1).toQuery()}`).join(' And ')} Then ${args.fn(arg).toQuery()}`).toQuery() query += args.knex
.raw(
`\nWhen ${args.pt.arguments
.filter((_, j) => +i !== j)
.map(
arg1 => `${args.fn(arg).toQuery()} > ${args.fn(arg1).toQuery()}`
)
.join(' And ')} Then ${args.fn(arg).toQuery()}`
)
.toQuery();
} }
} }
return args.knex.raw(`Case ${query}\n End${args.colAlias}`) return args.knex.raw(`Case ${query}\n End${args.colAlias}`);
}, },
MOD: (pt) => { MOD: pt => {
Object.assign(pt, { Object.assign(pt, {
type: 'BinaryExpression', type: 'BinaryExpression',
operator: '%', operator: '%',
left: pt.arguments[0], left: pt.arguments[0],
right: pt.arguments[1] right: pt.arguments[1]
}) });
}, },
REPEAT: 'REPLICATE', REPEAT: 'REPLICATE',
NOW: 'getdate', NOW: 'getdate',
SEARCH: (args: MapFnArgs) => { SEARCH: (args: MapFnArgs) => {
args.pt.callee.name = 'CHARINDEX'; args.pt.callee.name = 'CHARINDEX';
const temp = args.pt.arguments[0] const temp = args.pt.arguments[0];
args.pt.arguments[0] = args.pt.arguments[1] args.pt.arguments[0] = args.pt.arguments[1];
args.pt.arguments[1] = temp; args.pt.arguments[1] = temp;
}, },
INT: (args: MapFnArgs) => { INT: (args: MapFnArgs) => {
return args.knex.raw(`CASE WHEN ISNUMERIC(${args.fn(args.pt.arguments[0]).toQuery()}) = 1 THEN FLOOR(${args.fn(args.pt.arguments[0]).toQuery()}) ELSE 0 END${args.colAlias}`) return args.knex.raw(
`CASE WHEN ISNUMERIC(${args
.fn(args.pt.arguments[0])
.toQuery()}) = 1 THEN FLOOR(${args
.fn(args.pt.arguments[0])
.toQuery()}) ELSE 0 END${args.colAlias}`
);
}, },
MID:'SUBSTR', MID: 'SUBSTR',
FLOAT: (args: MapFnArgs) => { FLOAT: (args: MapFnArgs) => {
return args.knex.raw(`CAST(${args.fn(args.pt.arguments[0])} as FLOAT)${args.colAlias}`).wrap('(',')') return args.knex
}} .raw(`CAST(${args.fn(args.pt.arguments[0])} as FLOAT)${args.colAlias}`)
.wrap('(', ')');
}
};
export default mssql; export default mssql;

41
packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/mysql.ts

@ -1,6 +1,5 @@
import {MapFnArgs} from "../mapFunctionName"; import { MapFnArgs } from '../mapFunctionName';
import commonFns from "./commonFns"; import commonFns from './commonFns';
const mysql2 = { const mysql2 = {
...commonFns, ...commonFns,
@ -9,25 +8,35 @@ const mysql2 = {
MAX: 'GREATEST', MAX: 'GREATEST',
SEARCH: (args: MapFnArgs) => { SEARCH: (args: MapFnArgs) => {
args.pt.callee.name = 'LOCATE'; args.pt.callee.name = 'LOCATE';
const temp = args.pt.arguments[0] const temp = args.pt.arguments[0];
args.pt.arguments[0] = args.pt.arguments[1] args.pt.arguments[0] = args.pt.arguments[1];
args.pt.arguments[1] = temp; args.pt.arguments[1] = temp;
}, },
INT:(args: MapFnArgs) =>{ INT: (args: MapFnArgs) => {
return args.knex.raw(`CAST(${args.fn(args.pt.arguments[0])} as SIGNED)${args.colAlias}`) return args.knex.raw(
`CAST(${args.fn(args.pt.arguments[0])} as SIGNED)${args.colAlias}`
);
}, },
LEFT:(args: MapFnArgs)=> { LEFT: (args: MapFnArgs) => {
return args.knex.raw(`SUBSTR(${args.fn(args.pt.arguments[0])},1,${args.fn(args.pt.arguments[1])})${args.colAlias}`) return args.knex.raw(
`SUBSTR(${args.fn(args.pt.arguments[0])},1,${args.fn(
args.pt.arguments[1]
)})${args.colAlias}`
);
}, },
RIGHT:(args: MapFnArgs)=> { RIGHT: (args: MapFnArgs) => {
return args.knex.raw(`SUBSTR(${args.fn(args.pt.arguments[0])},-${args.fn(args.pt.arguments[1])})${args.colAlias}`) return args.knex.raw(
`SUBSTR(${args.fn(args.pt.arguments[0])},-${args.fn(
args.pt.arguments[1]
)})${args.colAlias}`
);
}, },
MID:'SUBSTR', MID: 'SUBSTR',
FLOAT: (args: MapFnArgs) => { FLOAT: (args: MapFnArgs) => {
return args.knex.raw(`CAST(${args.fn(args.pt.arguments[0])} as DOUBLE)${args.colAlias}`).wrap('(',')') return args.knex
.raw(`CAST(${args.fn(args.pt.arguments[0])} as DOUBLE)${args.colAlias}`)
.wrap('(', ')');
} }
} };
export default mysql2; export default mysql2;

29
packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/pg.ts

@ -1,5 +1,5 @@
import {MapFnArgs} from "../mapFunctionName"; import { MapFnArgs } from '../mapFunctionName';
import commonFns from "./commonFns"; import commonFns from './commonFns';
const pg = { const pg = {
...commonFns, ...commonFns,
@ -11,17 +11,32 @@ const pg = {
POWER: 'pow', POWER: 'pow',
SQRT: 'sqrt', SQRT: 'sqrt',
SEARCH: (args: MapFnArgs) => { SEARCH: (args: MapFnArgs) => {
return args.knex.raw(`POSITION(${args.knex.raw(args.fn(args.pt.arguments[1]).toQuery())} in ${args.knex.raw(args.fn(args.pt.arguments[0]).toQuery())})${args.colAlias}`) return args.knex.raw(
`POSITION(${args.knex.raw(
args.fn(args.pt.arguments[1]).toQuery()
)} in ${args.knex.raw(args.fn(args.pt.arguments[0]).toQuery())})${
args.colAlias
}`
);
}, },
INT(args: MapFnArgs) { INT(args: MapFnArgs) {
// todo: correction // todo: correction
return args.knex.raw(`REGEXP_REPLACE(COALESCE(${args.fn(args.pt.arguments[0])}::character varying, '0'), '[^0-9]+|\\.[0-9]+' ,'')${args.colAlias}`) return args.knex.raw(
`REGEXP_REPLACE(COALESCE(${args.fn(
args.pt.arguments[0]
)}::character varying, '0'), '[^0-9]+|\\.[0-9]+' ,'')${args.colAlias}`
);
}, },
MID: 'SUBSTR', MID: 'SUBSTR',
FLOAT: (args: MapFnArgs) => { FLOAT: (args: MapFnArgs) => {
return args.knex.raw(`CAST(${args.fn(args.pt.arguments[0])} as DOUBLE PRECISION)${args.colAlias}`).wrap('(',')') return args.knex
.raw(
`CAST(${args.fn(args.pt.arguments[0])} as DOUBLE PRECISION)${
args.colAlias
}`
)
.wrap('(', ')');
} }
} };
export default pg; export default pg;

47
packages/nocodb/src/lib/dataMapper/lib/sql/functionMappings/sqlite.ts

@ -1,14 +1,18 @@
import {MapFnArgs} from "../mapFunctionName"; import { MapFnArgs } from '../mapFunctionName';
import commonFns from "./commonFns"; import commonFns from './commonFns';
const sqlite3 = { const sqlite3 = {
...commonFns, ...commonFns,
LEN: 'LENGTH', LEN: 'LENGTH',
CEILING(args) { CEILING(args) {
return args.knex.raw(`round(${args.fn(args.pt.arguments[0])} + 0.5)${args.colAlias}`) return args.knex.raw(
}, FLOOR(args) { `round(${args.fn(args.pt.arguments[0])} + 0.5)${args.colAlias}`
return args.knex.raw(`round(${args.fn(args.pt.arguments[0])} - 0.5)${args.colAlias}`) );
},
FLOOR(args) {
return args.knex.raw(
`round(${args.fn(args.pt.arguments[0])} - 0.5)${args.colAlias}`
);
}, },
MOD: (args: MapFnArgs) => { MOD: (args: MapFnArgs) => {
return args.fn({ return args.fn({
@ -16,27 +20,42 @@ const sqlite3 = {
operator: '%', operator: '%',
left: args.pt.arguments[0], left: args.pt.arguments[0],
right: args.pt.arguments[1] right: args.pt.arguments[1]
}) });
}, },
REPEAT(args: MapFnArgs) { REPEAT(args: MapFnArgs) {
return args.knex.raw(`replace(printf('%.' || ${args.fn(args.pt.arguments[1])} || 'c', '/'),'/',${args.fn(args.pt.arguments[0])})${args.colAlias}`) return args.knex.raw(
`replace(printf('%.' || ${args.fn(
args.pt.arguments[1]
)} || 'c', '/'),'/',${args.fn(args.pt.arguments[0])})${args.colAlias}`
);
}, },
NOW: 'DATE', NOW: 'DATE',
SEARCH: 'INSTR', SEARCH: 'INSTR',
INT(args: MapFnArgs) { INT(args: MapFnArgs) {
return args.knex.raw(`CAST(${args.fn(args.pt.arguments[0])} as INTEGER)${args.colAlias}`) return args.knex.raw(
`CAST(${args.fn(args.pt.arguments[0])} as INTEGER)${args.colAlias}`
);
}, },
LEFT: (args: MapFnArgs) => { LEFT: (args: MapFnArgs) => {
return args.knex.raw(`SUBSTR(${args.fn(args.pt.arguments[0])},1,${args.fn(args.pt.arguments[1])})${args.colAlias}`) return args.knex.raw(
`SUBSTR(${args.fn(args.pt.arguments[0])},1,${args.fn(
args.pt.arguments[1]
)})${args.colAlias}`
);
}, },
RIGHT: (args: MapFnArgs) => { RIGHT: (args: MapFnArgs) => {
return args.knex.raw(`SUBSTR(${args.fn(args.pt.arguments[0])},-${args.fn(args.pt.arguments[1])})${args.colAlias}`) return args.knex.raw(
`SUBSTR(${args.fn(args.pt.arguments[0])},-${args.fn(
args.pt.arguments[1]
)})${args.colAlias}`
);
}, },
MID: 'SUBSTR', MID: 'SUBSTR',
FLOAT: (args: MapFnArgs) => { FLOAT: (args: MapFnArgs) => {
return args.knex.raw(`CAST(${args.fn(args.pt.arguments[0])} as FLOAT)${args.colAlias}`).wrap('(',')') return args.knex
.raw(`CAST(${args.fn(args.pt.arguments[0])} as FLOAT)${args.colAlias}`)
.wrap('(', ')');
} }
} };
export default sqlite3; export default sqlite3;

29
packages/nocodb/src/lib/dataMapper/lib/sql/genRollupSelect.ts

@ -1,25 +1,32 @@
import Knex from "knex"; import Knex from 'knex';
export default function ({knex, rollup,}: { knex: Knex, rollup: any }) { export default function({ knex, rollup }: { knex: Knex; rollup: any }) {
switch (rollup.type) { switch (rollup.type) {
case 'hm': case 'hm':
return knex(rollup.rltn) return knex(rollup.rltn)
[rollup.fn]?.(knex.ref(`${rollup.rltn}.${rollup.rlcn}`)) [rollup.fn]?.(knex.ref(`${rollup.rltn}.${rollup.rlcn}`))
.where( .where(
knex.ref(`${rollup.tn}.${rollup.cn}`), '=', knex.ref(`${rollup.rtn}.${rollup.rcn}`) knex.ref(`${rollup.tn}.${rollup.cn}`),
) '=',
knex.ref(`${rollup.rtn}.${rollup.rcn}`)
);
break; break;
case 'mm': case 'mm':
return knex(rollup.rltn) return knex(rollup.rltn)
[rollup.fn]?.(knex.ref(`${rollup.rltn}.${rollup.rlcn}`)) [rollup.fn]?.(knex.ref(`${rollup.rltn}.${rollup.rlcn}`))
.innerJoin(rollup.vtn, knex.ref(`${rollup.vtn}.${rollup.vrcn}`), '=', knex.ref(`${rollup.rtn}.${rollup.rcn}`)) .innerJoin(
.where(knex.ref(`${rollup.vtn}.${rollup.vcn}`), '=', knex.ref(`${rollup.tn}.${rollup.cn}`)) rollup.vtn,
knex.ref(`${rollup.vtn}.${rollup.vrcn}`),
'=',
knex.ref(`${rollup.rtn}.${rollup.rcn}`)
)
.where(
knex.ref(`${rollup.vtn}.${rollup.vcn}`),
'=',
knex.ref(`${rollup.tn}.${rollup.cn}`)
);
default: default:
throw Error(`Unsupported relation type '${rollup.type}'`) throw Error(`Unsupported relation type '${rollup.type}'`);
} }
} }

34
packages/nocodb/src/lib/dataMapper/lib/sql/mapFunctionName.ts

@ -1,23 +1,22 @@
import {XKnex} from "../../index"; import { XKnex } from '../../index';
import mssql from "./functionMappings/mssql"; import mssql from './functionMappings/mssql';
import mysql from "./functionMappings/mysql"; import mysql from './functionMappings/mysql';
import pg from "./functionMappings/pg"; import pg from './functionMappings/pg';
import sqlite from "./functionMappings/sqlite"; import sqlite from './functionMappings/sqlite';
import {QueryBuilder} from "knex"; import { QueryBuilder } from 'knex';
export interface MapFnArgs { export interface MapFnArgs {
pt: any, pt: any;
aliasToCol: { [alias: string]: string }, aliasToCol: { [alias: string]: string };
knex: XKnex, knex: XKnex;
alias: string, alias: string;
a?: string, a?: string;
fn: (...args: any) => QueryBuilder | any, fn: (...args: any) => QueryBuilder | any;
colAlias: string, colAlias: string;
prevBinaryOp?: any prevBinaryOp?: any;
} }
const mapFunctionName = (args: MapFnArgs): any => { const mapFunctionName = (args: MapFnArgs): any => {
const name = args.pt.callee.name; const name = args.pt.callee.name;
let val; let val;
@ -40,11 +39,10 @@ const mapFunctionName = (args: MapFnArgs): any => {
} }
if (typeof val === 'function') { if (typeof val === 'function') {
return val(args) return val(args);
} else if (typeof val === 'string') { } else if (typeof val === 'string') {
args.pt.callee.name = val; args.pt.callee.name = val;
} }
} };
export default mapFunctionName; export default mapFunctionName;

9
packages/nocodb/src/lib/index.ts

@ -1,15 +1,10 @@
import Noco from './noco/Noco' import Noco from './noco/Noco';
import XcTry from './noco/nc.try'; import XcTry from './noco/nc.try';
import NcConfigFactory from './utils/NcConfigFactory'; import NcConfigFactory from './utils/NcConfigFactory';
export default Noco; export default Noco;
export { export { Noco, NcConfigFactory, XcTry };
Noco,
NcConfigFactory,
XcTry
};
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd

660
packages/nocodb/src/lib/migrator/SqlMigrator/lib/KnexMigrator.ts

File diff suppressed because it is too large Load Diff

5
packages/nocodb/src/lib/migrator/SqlMigrator/lib/SqlMigrator.ts

@ -5,9 +5,7 @@ export default class SqlMigrator {
this.project = null; this.project = null;
} }
init(_project = null) { init(_project = null) {}
}
// migrationsInit() { // migrationsInit() {
// //
@ -36,7 +34,6 @@ export default class SqlMigrator {
// migrationsDelete() { // migrationsDelete() {
// //
// } // }
} }
/** /**

16
packages/nocodb/src/lib/migrator/SqlMigrator/lib/SqlMigratorFactory.ts

@ -1,18 +1,18 @@
import KnexMigrator from "./KnexMigrator"; import KnexMigrator from './KnexMigrator';
export default class SqlMigratorFactory { export default class SqlMigratorFactory {
static create(args) { static create(args) {
switch (args.client) { switch (args.client) {
case "mysql": case 'mysql':
case "mysql2": case 'mysql2':
case "pg": case 'pg':
case "oracledb": case 'oracledb':
case "mssql": case 'mssql':
case "sqlite3": case 'sqlite3':
return new KnexMigrator(); return new KnexMigrator();
break; break;
default: default:
throw new Error("Database not supported"); throw new Error('Database not supported');
break; break;
} }
} }

42
packages/nocodb/src/lib/migrator/SqlMigrator/lib/templates/mssql.template.ts

@ -1,48 +1,48 @@
module.exports = { module.exports = {
title: "default", title: 'default',
envs: { envs: {
_noco: { _noco: {
api:{}, api: {},
db: [ db: [
{ {
client: "mssql", client: 'mssql',
connection: { connection: {
host: process.env.DOCKER_DB_HOST || "localhost", host: process.env.DOCKER_DB_HOST || 'localhost',
port: process.env.DOCKER_DB_PORT ? parseInt(process.env.DOCKER_DB_PORT,10) : null || 1433, port: process.env.DOCKER_DB_PORT
user: "sa", ? parseInt(process.env.DOCKER_DB_PORT, 10)
password: "Password123.", : null || 1433,
database: "default_dev" user: 'sa',
password: 'Password123.',
database: 'default_dev'
}, },
meta: { meta: {
tn: "nc_evolutions", tn: 'nc_evolutions',
dbAlias: "primary" dbAlias: 'primary'
} }
} }
] ]
}, },
test: { test: {
api:{}, api: {},
db: [ db: [
{ {
client: "mssql", client: 'mssql',
connection: { connection: {
host: DOCKER_DB_HOST || "localhost", host: DOCKER_DB_HOST || 'localhost',
port: DOCKER_DB_PORT ? parseInt(DOCKER_DB_PORT) : null || 1433, port: DOCKER_DB_PORT ? parseInt(DOCKER_DB_PORT) : null || 1433,
user: "sa", user: 'sa',
password: "Password123.", password: 'Password123.',
database: "default_test" database: 'default_test'
}, },
meta: { meta: {
tn: "nc_evolutions", tn: 'nc_evolutions',
dbAlias: "primary" dbAlias: 'primary'
} }
} }
] ]
} }
}, },
workingEnv: "_noco", workingEnv: '_noco',
meta: { meta: {
version: '0.5', version: '0.5',
seedsFolder: 'seeds', seedsFolder: 'seeds',

34
packages/nocodb/src/lib/migrator/SqlMigrator/lib/templates/mysql.template.ts

@ -1,22 +1,20 @@
module.exports = { module.exports = {
title: "default", title: 'default',
envs: { envs: {
_noco: { _noco: {
db: [ db: [
{ {
client: "mysql2", client: 'mysql2',
connection: { connection: {
host: process.env.DOCKER_DB_HOST || "localhost", host: process.env.DOCKER_DB_HOST || 'localhost',
port: process.env.DOCKER_DB_PORT || 3306, port: process.env.DOCKER_DB_PORT || 3306,
user: "root", user: 'root',
password: "password", password: 'password',
database: "default_dev" database: 'default_dev'
}, },
meta: { meta: {
tn: "nc_evolutions", tn: 'nc_evolutions',
dbAlias: "primary" dbAlias: 'primary'
} }
} }
] ]
@ -25,23 +23,23 @@ module.exports = {
api: {}, api: {},
db: [ db: [
{ {
client: "mysql2", client: 'mysql2',
connection: { connection: {
host: DOCKER_DB_HOST || "localhost", host: DOCKER_DB_HOST || 'localhost',
port: DOCKER_DB_PORT || 3306, port: DOCKER_DB_PORT || 3306,
user: "root", user: 'root',
password: "password", password: 'password',
database: "default_test" database: 'default_test'
}, },
meta: { meta: {
tn: "nc_evolutions", tn: 'nc_evolutions',
dbAlias: "primary" dbAlias: 'primary'
} }
} }
] ]
} }
}, },
workingEnv: "_noco", workingEnv: '_noco',
meta: { meta: {
version: '0.5', version: '0.5',
seedsFolder: 'seeds', seedsFolder: 'seeds',

34
packages/nocodb/src/lib/migrator/SqlMigrator/lib/templates/pg.template.ts

@ -1,22 +1,22 @@
const {DOCKER_DB_HOST, DOCKER_DB_PORT} = process.env; const { DOCKER_DB_HOST, DOCKER_DB_PORT } = process.env;
module.exports = { module.exports = {
title: "default", title: 'default',
envs: { envs: {
_noco: { _noco: {
db: [ db: [
{ {
client: "pg", client: 'pg',
connection: { connection: {
host: DOCKER_DB_HOST || "localhost", host: DOCKER_DB_HOST || 'localhost',
port: DOCKER_DB_PORT || 5432, port: DOCKER_DB_PORT || 5432,
user: "postgres", user: 'postgres',
password: "password", password: 'password',
database: "default_dev" database: 'default_dev'
}, },
meta: { meta: {
tn: "nc_evolutions", tn: 'nc_evolutions',
dbAlias: "primary" dbAlias: 'primary'
} }
} }
] ]
@ -24,23 +24,23 @@ module.exports = {
test: { test: {
db: [ db: [
{ {
client: "pg", client: 'pg',
connection: { connection: {
host: DOCKER_DB_HOST || "localhost", host: DOCKER_DB_HOST || 'localhost',
port: DOCKER_DB_PORT || 5432, port: DOCKER_DB_PORT || 5432,
user: "postgres", user: 'postgres',
password: "password", password: 'password',
database: "default_test" database: 'default_test'
}, },
meta: { meta: {
tn: "nc_evolutions", tn: 'nc_evolutions',
dbAlias: "primary" dbAlias: 'primary'
} }
} }
] ]
} }
}, },
workingEnv: "_noco", workingEnv: '_noco',
meta: { meta: {
version: '0.5', version: '0.5',
seedsFolder: 'seeds', seedsFolder: 'seeds',

28
packages/nocodb/src/lib/migrator/SqlMigrator/lib/templates/sqlite.template.ts

@ -1,26 +1,26 @@
import path from "path"; import path from 'path';
const {DOCKER_DB_FILE} = process.env; const { DOCKER_DB_FILE } = process.env;
module.exports = { module.exports = {
title: "default", title: 'default',
envs: { envs: {
_noco: { _noco: {
db: [ db: [
{ {
client: "sqlite3", client: 'sqlite3',
connection: { connection: {
client: "sqlite3", client: 'sqlite3',
connection: { connection: {
filename: filename:
DOCKER_DB_FILE || DOCKER_DB_FILE ||
`${path.join(process.cwd(), "xmigrator", "default_dev.db")}` `${path.join(process.cwd(), 'xmigrator', 'default_dev.db')}`
}, },
useNullAsDefault: true useNullAsDefault: true
}, },
meta: { meta: {
tn: "nc_evolutions", tn: 'nc_evolutions',
dbAlias: "primary" dbAlias: 'primary'
} }
} }
] ]
@ -29,25 +29,25 @@ module.exports = {
api: {}, api: {},
db: [ db: [
{ {
client: "sqlite3", client: 'sqlite3',
connection: { connection: {
client: "sqlite3", client: 'sqlite3',
connection: { connection: {
filename: filename:
DOCKER_DB_FILE || DOCKER_DB_FILE ||
`${path.join(process.cwd(), "xmigrator", "default_test.db")}` `${path.join(process.cwd(), 'xmigrator', 'default_test.db')}`
}, },
useNullAsDefault: true useNullAsDefault: true
}, },
meta: { meta: {
tn: "nc_evolutions", tn: 'nc_evolutions',
dbAlias: "primary" dbAlias: 'primary'
} }
} }
] ]
} }
}, },
workingEnv: "_noco", workingEnv: '_noco',
meta: { meta: {
version: '0.5', version: '0.5',
seedsFolder: 'seeds', seedsFolder: 'seeds',

37
packages/nocodb/src/lib/migrator/util/Debug.ts

@ -1,17 +1,16 @@
import boxen from "boxen"; import boxen from 'boxen';
import debug from "debug"; import debug from 'debug';
import("colors"); import('colors');
import DebugMgr from "./DebugMgr"; import DebugMgr from './DebugMgr';
export default class Debug { export default class Debug {
public namespace: any;
public namespace:any; public api: any;
public api:any; public warn: any;
public warn:any; public info: any;
public info:any; public error: any;
public error:any; public debug: any;
public debug:any;
constructor(namespace) { constructor(namespace) {
this.namespace = namespace; this.namespace = namespace;
@ -25,25 +24,23 @@ export default class Debug {
} }
ppException(e, func = null) { ppException(e, func = null) {
let log = ""; let log = '';
log += ` EXCEPTION OCCURED!! in ${ log += ` EXCEPTION OCCURED!! in ${this.namespace.red.bold} @ ${func}`;
this.namespace.red.bold log += '\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n'
} @ ${func}`;
log += "\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n"
.red.bold; .red.bold;
log += `MESSAGE:\n`.yellow.bold; log += `MESSAGE:\n`.yellow.bold;
log += `${e.message}\n`.yellow.bold; log += `${e.message}\n`.yellow.bold;
log += "\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n" log += '\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n'
.red.bold; .red.bold;
log += `CODE:\n`.yellow.bold; log += `CODE:\n`.yellow.bold;
log += `${e.code}\n`.yellow.bold; log += `${e.code}\n`.yellow.bold;
log += "\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n" log += '\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n'
.red.bold; .red.bold;
log += `STACK:\n`.yellow.bold; log += `STACK:\n`.yellow.bold;
log += `${e.stack}\n`.yellow.bold; log += `${e.stack}\n`.yellow.bold;
log += "\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n" log += '\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n'
.red.bold; .red.bold;
console.log(boxen(log, { padding: 1, borderStyle: "double" })); console.log(boxen(log, { padding: 1, borderStyle: 'double' }));
console.log(e); console.log(e);
return log; return log;
} }

24
packages/nocodb/src/lib/migrator/util/DebugMgr.ts

@ -1,13 +1,13 @@
import debug from "debug"; import debug from 'debug';
const namespaces = {}; const namespaces = {};
const levels = { const levels = {
api: "A", api: 'A',
info: "I", info: 'I',
error: "E", error: 'E',
warn: "W", warn: 'W',
debug: "D" debug: 'D'
}; };
export default class DebugMgr { export default class DebugMgr {
static _create(namespace) { static _create(namespace) {
@ -31,23 +31,23 @@ export default class DebugMgr {
namespaces[namespace] = {}; namespaces[namespace] = {};
namespaces[namespace][`${namespace}_A`] = { namespaces[namespace][`${namespace}_A`] = {
level: "api", level: 'api',
enabled: debug.enabled(`${namespace}_A`) enabled: debug.enabled(`${namespace}_A`)
}; };
namespaces[namespace][`${namespace}_W`] = { namespaces[namespace][`${namespace}_W`] = {
level: "warn", level: 'warn',
enabled: debug.enabled(`${namespace}_W`) enabled: debug.enabled(`${namespace}_W`)
}; };
namespaces[namespace][`${namespace}_I`] = { namespaces[namespace][`${namespace}_I`] = {
level: "info", level: 'info',
enabled: debug.enabled(`${namespace}_I`) enabled: debug.enabled(`${namespace}_I`)
}; };
namespaces[namespace][`${namespace}_E`] = { namespaces[namespace][`${namespace}_E`] = {
level: "error", level: 'error',
enabled: debug.enabled(`${namespace}_E`) enabled: debug.enabled(`${namespace}_E`)
}; };
namespaces[namespace][`${namespace}_D`] = { namespaces[namespace][`${namespace}_D`] = {
level: "debug", level: 'debug',
enabled: debug.enabled(`${namespace}_D`) enabled: debug.enabled(`${namespace}_D`)
}; };
} }
@ -77,7 +77,7 @@ export default class DebugMgr {
static disable(namespace, level) { static disable(namespace, level) {
const toBeRemoved = `${namespace}_${levels[level]}`; const toBeRemoved = `${namespace}_${levels[level]}`;
let list = `${debug.disable()}`; let list = `${debug.disable()}`;
list = list.replace(toBeRemoved, ""); list = list.replace(toBeRemoved, '');
debug.enable(list); debug.enable(list);
this.refreshNamespace(namespace); this.refreshNamespace(namespace);
} }

12
packages/nocodb/src/lib/migrator/util/FileCollection.ts

@ -1,11 +1,11 @@
import fs from 'fs'; import fs from 'fs';
import { promisify } from "util"; import { promisify } from 'util';
import jsonfile from 'jsonfile'; import jsonfile from 'jsonfile';
export default class FileCollection { export default class FileCollection {
public args; public args;
public path; public path;
constructor(args) { constructor(args) {
this.args = args; this.args = args;
this.path = args.path; this.path = args.path;
@ -18,7 +18,7 @@ public path;
*/ */
const exists = await promisify(fs.exists)(this.args.path); const exists = await promisify(fs.exists)(this.args.path);
if(!exists) { if (!exists) {
await promisify(jsonfile.writeFile)(this.args.path, [], { await promisify(jsonfile.writeFile)(this.args.path, [], {
spaces: 2 spaces: 2
}); });
@ -29,17 +29,13 @@ public path;
return await promisify(jsonfile.readFile)(this.args.path); return await promisify(jsonfile.readFile)(this.args.path);
} }
async write(args) { async write(args) {
await promisify(jsonfile.writeFile)(this.args.path, args.data, { await promisify(jsonfile.writeFile)(this.args.path, args.data, {
spaces: 2 spaces: 2
}); });
} }
} }
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *

8
packages/nocodb/src/lib/migrator/util/Result.ts

@ -1,8 +1,8 @@
export default class Result { export default class Result {
public code:any; public code: any;
public message:any; public message: any;
public data:any; public data: any;
public object:any; public object: any;
constructor(code = 0, message = '', data = {}) { constructor(code = 0, message = '', data = {}) {
this.code = code; this.code = code;
this.message = message; this.message = message;

4
packages/nocodb/src/lib/migrator/util/emit.ts

@ -1,9 +1,9 @@
import Emittery from "emittery"; import Emittery from 'emittery';
let emitSingleton = null; let emitSingleton = null;
export default class Emit { export default class Emit {
public evt:any; public evt: any;
constructor() { constructor() {
if (emitSingleton) return emitSingleton; if (emitSingleton) return emitSingleton;

29
packages/nocodb/src/lib/migrator/util/file.help.ts

@ -1,30 +1,19 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const getUniqFilenamePrefix = function() {
const getUniqFilenamePrefix = function () { return dayjs().format('YYYYMMDD_HHmmssSSS');
return dayjs().format('YYYYMMDD_HHmmssSSS')
}; };
const getFilenameForUp = function (prefix) { const getFilenameForUp = function(prefix) {
return prefix + '.up.sql';
return prefix + '.up.sql'
}; };
const getFilenameForDown = function (prefix) { const getFilenameForDown = function(prefix) {
return prefix + '.down.sql';
return prefix + '.down.sql' };
}
export { export { getUniqFilenamePrefix, getFilenameForUp, getFilenameForDown };
getUniqFilenamePrefix, /**
getFilenameForUp,
getFilenameForDown
}
;/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>

434
packages/nocodb/src/lib/noco/NcProjectBuilder.ts

@ -1,21 +1,20 @@
import fs from "fs"; import fs from 'fs';
import path from "path"; import path from 'path';
import axios from "axios"; import axios from 'axios';
import {Router} from "express"; import { Router } from 'express';
import {SqlClientFactory, Tele} from 'nc-help'; import { SqlClientFactory, Tele } from 'nc-help';
import {NcConfig} from "../../interface/config"; import { NcConfig } from '../../interface/config';
import Migrator from '../migrator/SqlMigrator/lib/KnexMigrator'; import Migrator from '../migrator/SqlMigrator/lib/KnexMigrator';
import Noco from "./Noco"; import Noco from './Noco';
import {GqlApiBuilder} from "./gql/GqlApiBuilder"; import { GqlApiBuilder } from './gql/GqlApiBuilder';
import {XCEeError} from "./meta/NcMetaMgr"; import { XCEeError } from './meta/NcMetaMgr';
import {RestApiBuilder} from "./rest/RestApiBuilder"; import { RestApiBuilder } from './rest/RestApiBuilder';
import NcConnectionMgr from "./common/NcConnectionMgr"; import NcConnectionMgr from './common/NcConnectionMgr';
export default class NcProjectBuilder { export default class NcProjectBuilder {
public readonly id: string; public readonly id: string;
public readonly title: string; public readonly title: string;
public readonly description: string; public readonly description: string;
@ -38,16 +37,13 @@ export default class NcProjectBuilder {
this.id = project.id; this.id = project.id;
this.title = project.title; this.title = project.title;
this.description = project.description; this.description = project.description;
this._config = {...this.appConfig, ...JSON.parse(project.config)}; this._config = { ...this.appConfig, ...JSON.parse(project.config) };
this.router = Router(); this.router = Router();
} }
} }
public async init(isFirstTime?: boolean) { public async init(isFirstTime?: boolean) {
try { try {
await this.addAuthHookToMiddleware(); await this.addAuthHookToMiddleware();
this.startTime = Date.now(); this.startTime = Date.now();
@ -59,13 +55,16 @@ export default class NcProjectBuilder {
/* Create REST APIs / GraphQL Resolvers */ /* Create REST APIs / GraphQL Resolvers */
for (const meta of this.apiBuilders) { for (const meta of this.apiBuilders) {
let routeInfo; let routeInfo;
if (meta instanceof RestApiBuilder) { if (meta instanceof RestApiBuilder) {
console.log(`Creating REST APIs ${meta.getDbType()} - > ${meta.getDbName()}`); console.log(
`Creating REST APIs ${meta.getDbType()} - > ${meta.getDbName()}`
);
routeInfo = await (meta as RestApiBuilder).init(); routeInfo = await (meta as RestApiBuilder).init();
} else if (meta instanceof GqlApiBuilder) { } else if (meta instanceof GqlApiBuilder) {
console.log(`Creating GraphQL APIs ${meta.getDbType()} - > ${meta.getDbName()}`); console.log(
`Creating GraphQL APIs ${meta.getDbType()} - > ${meta.getDbName()}`
);
routeInfo = await (meta as GqlApiBuilder).init(); routeInfo = await (meta as GqlApiBuilder).init();
} }
allRoutesInfo.push(routeInfo); allRoutesInfo.push(routeInfo);
@ -74,32 +73,44 @@ export default class NcProjectBuilder {
this.app.projectRouter.use(`/nc/${this.id}`, this.router); this.app.projectRouter.use(`/nc/${this.id}`, this.router);
await this.app.ncMeta.projectStatusUpdate(this.title, 'started'); await this.app.ncMeta.projectStatusUpdate(this.title, 'started');
} catch (e) { } catch (e) {
console.log(e); console.log(e);
await this.app.ncMeta.projectStatusUpdate(this.title, 'stopped'); await this.app.ncMeta.projectStatusUpdate(this.title, 'stopped');
} }
} }
public async handleRunTimeChanges(data: any): Promise<any> { public async handleRunTimeChanges(data: any): Promise<any> {
const curBuilder = this.apiBuilders.find(builder => { const curBuilder = this.apiBuilders.find(builder => {
return (data.req?.dbAlias || data.req?.args?.dbAlias) === builder.getDbAlias(); return (
(data.req?.dbAlias || data.req?.args?.dbAlias) === builder.getDbAlias()
);
}); });
switch (data?.req?.api) { switch (data?.req?.api) {
case 'xcAuthHookSet': case 'xcAuthHookSet':
this.authHook = await this.app.ncMeta.metaGet(this.id, 'db', 'nc_hooks', { this.authHook = await this.app.ncMeta.metaGet(
type: 'AUTH_MIDDLEWARE' this.id,
}); 'db',
'nc_hooks',
{
type: 'AUTH_MIDDLEWARE'
}
);
break; break;
case 'xcM2MRelationCreate': case 'xcM2MRelationCreate':
await curBuilder.onManyToManyRelationCreate(data.req.args.parentTable, data.req.args.childTable, data.req.args); await curBuilder.onManyToManyRelationCreate(
data.req.args.parentTable,
data.req.args.childTable,
data.req.args
);
break; break;
case 'relationCreate': case 'relationCreate':
await curBuilder.onRelationCreate(data.req.args.parentTable, data.req.args.childTable, data.req.args); await curBuilder.onRelationCreate(
data.req.args.parentTable,
data.req.args.childTable,
data.req.args
);
this.app.ncMeta.audit(this.id, curBuilder.getDbAlias(), 'nc_audit', { this.app.ncMeta.audit(this.id, curBuilder.getDbAlias(), 'nc_audit', {
op_type: 'RELATION', op_type: 'RELATION',
@ -108,11 +119,17 @@ export default class NcProjectBuilder {
description: `created relation between tables ${data.req.args.childTable} and ${data.req.args.parentTable} `, description: `created relation between tables ${data.req.args.childTable} and ${data.req.args.parentTable} `,
ip: data.ctx.req.clientIp ip: data.ctx.req.clientIp
}); });
console.log(`Added new relation between : ${data.req.args.parentTable} ==> ${data.req.args.childTable}`) console.log(
`Added new relation between : ${data.req.args.parentTable} ==> ${data.req.args.childTable}`
);
break; break;
case 'relationDelete': case 'relationDelete':
await curBuilder.onRelationDelete(data.req.args.parentTable, data.req.args.childTable, data.req.args); await curBuilder.onRelationDelete(
data.req.args.parentTable,
data.req.args.childTable,
data.req.args
);
this.app.ncMeta.audit(this.id, curBuilder.getDbAlias(), 'nc_audit', { this.app.ncMeta.audit(this.id, curBuilder.getDbAlias(), 'nc_audit', {
op_type: 'RELATION', op_type: 'RELATION',
op_sub_type: 'DELETED', op_sub_type: 'DELETED',
@ -120,29 +137,48 @@ export default class NcProjectBuilder {
description: `deleted relation between tables ${data.req.args.childTable} and ${data.req.args.parentTable} `, description: `deleted relation between tables ${data.req.args.childTable} and ${data.req.args.parentTable} `,
ip: data.ctx.req.clientIp ip: data.ctx.req.clientIp
}); });
console.log(`Deleted relation between : ${data.req.args.parentTable} ==> ${data.req.args.childTable}`) console.log(
`Deleted relation between : ${data.req.args.parentTable} ==> ${data.req.args.childTable}`
);
break; break;
case 'xcVirtualRelationCreate': case 'xcVirtualRelationCreate':
await curBuilder.onVirtualRelationCreate(data.req.args.parentTable, data.req.args.childTable); await curBuilder.onVirtualRelationCreate(
await curBuilder.onRelationCreate(data.req.args.parentTable, data.req.args.childTable, { data.req.args.parentTable,
...data.req.args, data.req.args.childTable
virtual: true );
}); await curBuilder.onRelationCreate(
console.log(`Added new relation between : ${data.req.args.parentTable} ==> ${data.req.args.childTable}`) data.req.args.parentTable,
data.req.args.childTable,
{
...data.req.args,
virtual: true
}
);
console.log(
`Added new relation between : ${data.req.args.parentTable} ==> ${data.req.args.childTable}`
);
break; break;
case 'xcVirtualRelationDelete': case 'xcVirtualRelationDelete':
await curBuilder.onRelationDelete(data.req.args.parentTable, data.req.args.childTable, { await curBuilder.onRelationDelete(
...data.req.args, data.req.args.parentTable,
virtual: true data.req.args.childTable,
}); {
console.log(`Added new relation between : ${data.req.args.parentTable} ==> ${data.req.args.childTable}`) ...data.req.args,
virtual: true
}
);
console.log(
`Added new relation between : ${data.req.args.parentTable} ==> ${data.req.args.childTable}`
);
break; break;
case 'xcRelationColumnDelete': case 'xcRelationColumnDelete':
if (data.req.args?.type === 'mm') { if (data.req.args?.type === 'mm') {
await curBuilder.onManyToManyRelationDelete(data.req.args.parentTable, data.req.args.childTable) await curBuilder.onManyToManyRelationDelete(
data.req.args.parentTable,
data.req.args.childTable
);
} }
break; break;
@ -154,7 +190,6 @@ export default class NcProjectBuilder {
await curBuilder.loadFormViews(); await curBuilder.loadFormViews();
break; break;
case 'tableCreate': case 'tableCreate':
await curBuilder.onTableCreate(data.req.args.tn, data.req.args); await curBuilder.onTableCreate(data.req.args.tn, data.req.args);
@ -164,8 +199,8 @@ export default class NcProjectBuilder {
user: data.user.email, user: data.user.email,
description: `created table ${data.req.args.tn} with alias ${data.req.args._tn} `, description: `created table ${data.req.args.tn} with alias ${data.req.args._tn} `,
ip: data.ctx.req.clientIp ip: data.ctx.req.clientIp
}) });
console.log(`Added new routes for table : ${data.req.args.tn}`) console.log(`Added new routes for table : ${data.req.args.tn}`);
break; break;
case 'viewCreate': case 'viewCreate':
@ -174,9 +209,10 @@ export default class NcProjectBuilder {
op_type: 'VIEW', op_type: 'VIEW',
op_sub_type: 'CREATED', op_sub_type: 'CREATED',
user: data.user.email, user: data.user.email,
description: `created view ${data.req.args.view_name} `, ip: data.ctx.req.clientIp description: `created view ${data.req.args.view_name} `,
}) ip: data.ctx.req.clientIp
console.log(`Added new routes for table : ${data.req.args.tn}`) });
console.log(`Added new routes for table : ${data.req.args.tn}`);
break; break;
case 'viewUpdate': case 'viewUpdate':
@ -185,9 +221,10 @@ export default class NcProjectBuilder {
op_type: 'VIEW', op_type: 'VIEW',
op_sub_type: 'UPDATED', op_sub_type: 'UPDATED',
user: data.user.email, user: data.user.email,
description: `updated view ${data.req.args.view_name} `, ip: data.ctx.req.clientIp description: `updated view ${data.req.args.view_name} `,
}) ip: data.ctx.req.clientIp
console.log(`Added new routes for table : ${data.req.args.tn}`) });
console.log(`Added new routes for table : ${data.req.args.tn}`);
break; break;
case 'tableDelete': case 'tableDelete':
@ -196,9 +233,10 @@ export default class NcProjectBuilder {
op_type: 'TABLE', op_type: 'TABLE',
op_sub_type: 'DELETED', op_sub_type: 'DELETED',
user: data.user.email, user: data.user.email,
description: `deleted table ${data.req.args.tn} `, ip: data.ctx.req.clientIp description: `deleted table ${data.req.args.tn} `,
}) ip: data.ctx.req.clientIp
console.log(`Deleted routes for table : ${data.req.args.tn}`) });
console.log(`Deleted routes for table : ${data.req.args.tn}`);
break; break;
case 'tableRename': case 'tableRename':
@ -210,50 +248,52 @@ export default class NcProjectBuilder {
user: data.user.email, user: data.user.email,
description: `renamed table ${data.req.args.tn_old} to ${data.req.args.tn} `, description: `renamed table ${data.req.args.tn_old} to ${data.req.args.tn} `,
ip: data.ctx.req.clientIp ip: data.ctx.req.clientIp
}) });
console.log(`Updated routes for table : ${data.req.args.tn}`) console.log(`Updated routes for table : ${data.req.args.tn}`);
break; break;
case 'xcRoutesHandlerUpdate': case 'xcRoutesHandlerUpdate':
case 'xcResolverHandlerUpdate': case 'xcResolverHandlerUpdate':
case 'xcRpcHandlerUpdate': case 'xcRpcHandlerUpdate':
// todo: implement separate function // todo: implement separate function
await curBuilder.onHandlerCodeUpdate(data.req.args.tn); await curBuilder.onHandlerCodeUpdate(data.req.args.tn);
console.log(`Updated routes handler for table : ${data.req.tn}`) console.log(`Updated routes handler for table : ${data.req.tn}`);
break; break;
case 'xcRoutesMiddlewareUpdate': case 'xcRoutesMiddlewareUpdate':
case 'xcResolverMiddlewareUpdate': case 'xcResolverMiddlewareUpdate':
// todo: implement separate function // todo: implement separate function
await curBuilder.onMiddlewareCodeUpdate(data.req.args.tn); await curBuilder.onMiddlewareCodeUpdate(data.req.args.tn);
console.log(`Updated routes handler for table : ${data.req.args.tn}`) console.log(`Updated routes handler for table : ${data.req.args.tn}`);
break; break;
case 'xcModelSet': case 'xcModelSet':
await curBuilder.onMetaUpdate(data.req.args.tn); await curBuilder.onMetaUpdate(data.req.args.tn);
console.log(`Updated validations for table : ${data.req.args.tn}`) console.log(`Updated validations for table : ${data.req.args.tn}`);
break; break;
case 'xcUpdateVirtualKeyAlias': case 'xcUpdateVirtualKeyAlias':
await curBuilder.onVirtualColumnAliasUpdate(data.req.args.tn); await curBuilder.onVirtualColumnAliasUpdate(data.req.args.tn);
console.log(`Updated validations for table : ${data.req.args.tn}`) console.log(`Updated validations for table : ${data.req.args.tn}`);
break; break;
case 'xcModelSchemaSet': case 'xcModelSchemaSet':
await curBuilder.onGqlSchemaUpdate(data.req.args.tn, data.req.args.schema); await curBuilder.onGqlSchemaUpdate(
console.log(`Updated validations for table : ${data.req.args.tn}`) data.req.args.tn,
data.req.args.schema
);
console.log(`Updated validations for table : ${data.req.args.tn}`);
break; break;
case 'tableXcHooksSet': case 'tableXcHooksSet':
await curBuilder.onHooksUpdate(data.req.args.tn); await curBuilder.onHooksUpdate(data.req.args.tn);
console.log(`Updated validations for table : ${data.req.args.tn}`) console.log(`Updated validations for table : ${data.req.args.tn}`);
break; break;
case 'xcModelSwaggerDocSet': case 'xcModelSwaggerDocSet':
await (curBuilder as RestApiBuilder).onSwaggerDocUpdate(data.req.args.tn); await (curBuilder as RestApiBuilder).onSwaggerDocUpdate(
console.log(`Updated validations for table : ${data.req.args.tn}`) data.req.args.tn
);
console.log(`Updated validations for table : ${data.req.args.tn}`);
break; break;
case 'tableUpdate': case 'tableUpdate':
@ -264,40 +304,40 @@ export default class NcProjectBuilder {
user: data.user.email, user: data.user.email,
description: `updated table ${data.req.args.tn} with alias ${data.req.args._tn} `, description: `updated table ${data.req.args.tn} with alias ${data.req.args._tn} `,
ip: data.ctx.req.clientIp ip: data.ctx.req.clientIp
}) });
console.log(`Updated validations for table : ${data.req.args.tn}`) console.log(`Updated validations for table : ${data.req.args.tn}`);
break; break;
case 'procedureCreate': case 'procedureCreate':
await curBuilder.onProcedureCreate(data.req.args.procedure_name) await curBuilder.onProcedureCreate(data.req.args.procedure_name);
break; break;
case 'functionUpdate': case 'functionUpdate':
await curBuilder.onFunctionDelete(data.req.args.function_name) await curBuilder.onFunctionDelete(data.req.args.function_name);
await curBuilder.onFunctionCreate(data.req.args.function_name) await curBuilder.onFunctionCreate(data.req.args.function_name);
break; break;
case 'procedureUpdate': case 'procedureUpdate':
await curBuilder.onProcedureDelete(data.req.args.procedure_name) await curBuilder.onProcedureDelete(data.req.args.procedure_name);
await curBuilder.onProcedureCreate(data.req.args.procedure_name) await curBuilder.onProcedureCreate(data.req.args.procedure_name);
break; break;
case 'procedureDelete': case 'procedureDelete':
await curBuilder.onProcedureDelete(data.req.args.procedure_name) await curBuilder.onProcedureDelete(data.req.args.procedure_name);
break; break;
case 'functionCreate': case 'functionCreate':
await curBuilder.onFunctionCreate(data.req.args.function_name) await curBuilder.onFunctionCreate(data.req.args.function_name);
break; break;
case 'functionDelete': case 'functionDelete':
await curBuilder.onFunctionDelete(data.req.args.function_name) await curBuilder.onFunctionDelete(data.req.args.function_name);
break; break;
case 'xcRoutesPolicyUpdate': case 'xcRoutesPolicyUpdate':
case 'xcResolverPolicyUpdate': case 'xcResolverPolicyUpdate':
await curBuilder.onPolicyUpdate(data.req.args.tn); await curBuilder.onPolicyUpdate(data.req.args.tn);
console.log(`Updated validations for table : ${data.req.args.tn}`) console.log(`Updated validations for table : ${data.req.args.tn}`);
break; break;
case 'xcModelsEnable': case 'xcModelsEnable':
@ -325,18 +365,17 @@ export default class NcProjectBuilder {
break; break;
case 'xcCronSave': case 'xcCronSave':
await curBuilder.restartCron(data.req.args) await curBuilder.restartCron(data.req.args);
break; break;
case 'cronDelete': case 'cronDelete':
await curBuilder.removeCron(data.req.args) await curBuilder.removeCron(data.req.args);
break; break;
// todo: optimize // todo: optimize
if (Array.isArray(data.req.args.tableNames)) { if (Array.isArray(data.req.args.tableNames)) {
for (const procedure of data.req.args.tableNames) { for (const procedure of data.req.args.tableNames) {
await curBuilder.onProcedureCreate(procedure) await curBuilder.onProcedureCreate(procedure);
} }
} }
case 'tableMetaCreate': case 'tableMetaCreate':
@ -355,13 +394,14 @@ export default class NcProjectBuilder {
XCEeError.throw(); XCEeError.throw();
break; break;
case 'viewDelete': case 'viewDelete':
await curBuilder.onViewDelete(data.req.args.view_name) await curBuilder.onViewDelete(data.req.args.view_name);
this.app.ncMeta.audit(this.id, curBuilder.getDbAlias(), 'nc_audit', { this.app.ncMeta.audit(this.id, curBuilder.getDbAlias(), 'nc_audit', {
op_type: 'VIEW', op_type: 'VIEW',
op_sub_type: 'DELETED', op_sub_type: 'DELETED',
user: data.user.email, user: data.user.email,
description: `deleted view ${data.req.args.view_name} `, ip: data.ctx.req.clientIp description: `deleted view ${data.req.args.view_name} `,
}) ip: data.ctx.req.clientIp
});
break; break;
case 'tableMetaRecreate': case 'tableMetaRecreate':
@ -391,28 +431,27 @@ export default class NcProjectBuilder {
break; break;
case 'procedureMetaDelete': case 'procedureMetaDelete':
await curBuilder.onProcedureDelete(data.req.args.procedure_name) await curBuilder.onProcedureDelete(data.req.args.procedure_name);
break; break;
case 'procedureMetaRecreate': case 'procedureMetaRecreate':
await curBuilder.onProcedureDelete(data.req.args.tn) await curBuilder.onProcedureDelete(data.req.args.tn);
await curBuilder.onProcedureCreate(data.req.args.tn) await curBuilder.onProcedureCreate(data.req.args.tn);
break; break;
case 'functionMetaCreate': case 'functionMetaCreate':
// todo: optimize // todo: optimize
if (Array.isArray(data.req.args.tableNames)) { if (Array.isArray(data.req.args.tableNames)) {
for (const functionName of data.req.args.tableNames) { for (const functionName of data.req.args.tableNames) {
await curBuilder.onFunctionCreate(functionName) await curBuilder.onFunctionCreate(functionName);
} }
} }
break; break;
case 'functionMetaDelete': case 'functionMetaDelete':
await curBuilder.onFunctionDelete(data.req.args.procedure_name) await curBuilder.onFunctionDelete(data.req.args.procedure_name);
break; break;
case 'projectStop': case 'projectStop':
this.router.stack.splice(0, this.router.stack.length); this.router.stack.splice(0, this.router.stack.length);
this.apiBuilders.splice(0, this.apiBuilders.length); this.apiBuilders.splice(0, this.apiBuilders.length);
@ -422,8 +461,9 @@ export default class NcProjectBuilder {
op_type: 'PROJECT', op_type: 'PROJECT',
op_sub_type: 'STOPPED', op_sub_type: 'STOPPED',
user: data.user.email, user: data.user.email,
description: `stopped project ${this.title}(${this.id}) `, ip: data?.ctx?.req?.clientIp description: `stopped project ${this.title}(${this.id}) `,
}) ip: data?.ctx?.req?.clientIp
});
break; break;
case 'projectStart': case 'projectStart':
@ -432,16 +472,21 @@ export default class NcProjectBuilder {
op_type: 'PROJECT', op_type: 'PROJECT',
op_sub_type: 'STARTED', op_sub_type: 'STARTED',
user: data.user.email, user: data.user.email,
description: `started project ${this.title}(${this.id}) `, ip: data?.ctx?.req?.clientIp description: `started project ${this.title}(${this.id}) `,
}) ip: data?.ctx?.req?.clientIp
});
break; break;
case 'projectDelete': case 'projectDelete':
this.router.stack.splice(0, this.router.stack.length); this.router.stack.splice(0, this.router.stack.length);
this.apiBuilders.splice(0, this.apiBuilders.length); this.apiBuilders.splice(0, this.apiBuilders.length);
await this.app.ncMeta.projectDeleteById(this.id); await this.app.ncMeta.projectDeleteById(this.id);
await this.app.ncMeta.knex('nc_projects_users').where({project_id: this.id}).del(); await this.app.ncMeta
for (const db of (this.config?.envs?.[this.appConfig?.workingEnv]?.db || [])) { .knex('nc_projects_users')
.where({ project_id: this.id })
.del();
for (const db of this.config?.envs?.[this.appConfig?.workingEnv]?.db ||
[]) {
const dbAlias = db?.meta?.dbAlias; const dbAlias = db?.meta?.dbAlias;
const apiType = db?.meta?.api?.type; const apiType = db?.meta?.api?.type;
await this.app.ncMeta.metaReset(this.id, dbAlias, apiType); await this.app.ncMeta.metaReset(this.id, dbAlias, apiType);
@ -451,8 +496,9 @@ export default class NcProjectBuilder {
op_type: 'PROJECT', op_type: 'PROJECT',
op_sub_type: 'DELETED', op_sub_type: 'DELETED',
user: data.user.email, user: data.user.email,
description: `deleted project ${this.title}(${this.id}) `, ip: data?.ctx?.req?.clientIp description: `deleted project ${this.title}(${this.id}) `,
}) ip: data?.ctx?.req?.clientIp
});
break; break;
case 'xcMetaTablesImportLocalFsToDb': case 'xcMetaTablesImportLocalFsToDb':
@ -463,18 +509,17 @@ export default class NcProjectBuilder {
op_type: 'PROJECT', op_type: 'PROJECT',
op_sub_type: 'RESTARTED', op_sub_type: 'RESTARTED',
user: data.user.email, user: data.user.email,
description: `restarted project ${this.title}(${this.id}) `, ip: data?.ctx?.req?.clientIp description: `restarted project ${this.title}(${this.id}) `,
}) ip: data?.ctx?.req?.clientIp
});
break; break;
default: default:
console.log('DB OPS', data.req.api); console.log('DB OPS', data.req.api);
} }
// export metadata to filesystem after meta changes // export metadata to filesystem after meta changes
switch (data?.req?.api) { switch (data?.req?.api) {
case 'procedureCreate': case 'procedureCreate':
case 'functionUpdate': case 'functionUpdate':
case 'procedureUpdate': case 'procedureUpdate':
@ -509,23 +554,20 @@ export default class NcProjectBuilder {
} }
break; break;
} }
} }
protected async _createApiBuilder() { protected async _createApiBuilder() {
this.apiBuilders.splice(0, this.apiBuilders.length); this.apiBuilders.splice(0, this.apiBuilders.length);
let i = 0; let i = 0;
const connectionConfigs = []; const connectionConfigs = [];
/* for each db create an api builder */ /* for each db create an api builder */
for (const db of (this.config?.envs?.[this.appConfig?.workingEnv]?.db || [])) { for (const db of this.config?.envs?.[this.appConfig?.workingEnv]?.db ||
[]) {
let Builder; let Builder;
switch (db.meta.api.type) { switch (db.meta.api.type) {
case "graphql": case 'graphql':
Builder = GqlApiBuilder; Builder = GqlApiBuilder;
break; break;
@ -535,7 +577,6 @@ export default class NcProjectBuilder {
} }
if ((db?.connection as any)?.database) { if ((db?.connection as any)?.database) {
const connectionConfig = { const connectionConfig = {
...db, ...db,
meta: { meta: {
@ -547,16 +588,22 @@ export default class NcProjectBuilder {
} }
}; };
this.apiBuilders.push(
this.apiBuilders.push(new Builder(this.app, this, this.config, connectionConfig, this.app.ncMeta)); new Builder(
this.app,
this,
this.config,
connectionConfig,
this.app.ncMeta
)
);
connectionConfigs.push(connectionConfig); connectionConfigs.push(connectionConfig);
i++; i++;
} else if (db.meta?.allSchemas) { } else if (db.meta?.allSchemas) {
/* get all schemas and create APIs for all of them */ /* get all schemas and create APIs for all of them */
const sqlClient = SqlClientFactory.create({ const sqlClient = SqlClientFactory.create({
...db, ...db,
connection: {...db.connection, database: undefined} connection: { ...db.connection, database: undefined }
}); });
// @ts-ignore // @ts-ignore
@ -564,7 +611,7 @@ export default class NcProjectBuilder {
for (const schema of schemaList) { for (const schema of schemaList) {
const connectionConfig = { const connectionConfig = {
...db, ...db,
connection: {...db.connection, database: schema.schema_name}, connection: { ...db.connection, database: schema.schema_name },
meta: { meta: {
...db.meta, ...db.meta,
dbAlias: i ? db.meta.dbAlias + i : db.meta.dbAlias, dbAlias: i ? db.meta.dbAlias + i : db.meta.dbAlias,
@ -575,54 +622,62 @@ export default class NcProjectBuilder {
} }
}; };
this.apiBuilders.push(new Builder(this.app, this, this.config, connectionConfig, this.app.ncMeta)); this.apiBuilders.push(
new Builder(
this.app,
this,
this.config,
connectionConfig,
this.app.ncMeta
)
);
connectionConfigs.push(connectionConfig); connectionConfigs.push(connectionConfig);
i++; i++;
} }
sqlClient.knex.destroy(); sqlClient.knex.destroy();
} }
} }
if (this.config?.envs?.[this.appConfig.workingEnv]?.db) { if (this.config?.envs?.[this.appConfig.workingEnv]?.db) {
this.config.envs[this.appConfig.workingEnv].db.splice(0, this.config.envs[this.appConfig.workingEnv].db.length, ...connectionConfigs); this.config.envs[this.appConfig.workingEnv].db.splice(
0,
this.config.envs[this.appConfig.workingEnv].db.length,
...connectionConfigs
);
} }
} }
protected genVer(i): string { protected genVer(i): string {
const l = 'vwxyzabcdefghijklmnopqrstu'; const l = 'vwxyzabcdefghijklmnopqrstu';
return i return (
.toString(26) i
.split('') .toString(26)
.map(v => l[parseInt(v, 26)]) .split('')
.join('') + '1'; .map(v => l[parseInt(v, 26)])
.join('') + '1'
);
} }
protected async syncMigration(): Promise<void> { protected async syncMigration(): Promise<void> {
if (
if (this.appConfig?.toolDir this.appConfig?.toolDir
// && !('NC_MIGRATIONS_DISABLED' in process.env) // && !('NC_MIGRATIONS_DISABLED' in process.env)
) { ) {
const dbs = this.config?.envs?.[this.appConfig.workingEnv]?.db;
const dbs = this.config?.envs?.[this.appConfig.workingEnv]?.db
if (!dbs || !dbs.length) { if (!dbs || !dbs.length) {
return; return;
} }
for (const connectionConfig of dbs) { for (const connectionConfig of dbs) {
try { try {
const sqlClient = NcConnectionMgr.getSqlClient({ const sqlClient = NcConnectionMgr.getSqlClient({
dbAlias: connectionConfig?.mets?.dbAlias, dbAlias: connectionConfig?.mets?.dbAlias,
env: this.config.env, env: this.config.env,
config: this.config, config: this.config,
projectId: this.id projectId: this.id
}) });
/* create migrator */ /* create migrator */
const migrator = new Migrator({ const migrator = new Migrator({
project_id: this.id, project_id: this.id,
@ -631,7 +686,13 @@ export default class NcProjectBuilder {
}); });
/* if migrator folder doesn't exist for project - call migratior init */ /* if migrator folder doesn't exist for project - call migratior init */
const migrationFolder = path.join(this.config.toolDir, 'nc', this.id, connectionConfig.meta.dbAlias, 'migrations'); const migrationFolder = path.join(
this.config.toolDir,
'nc',
this.id,
connectionConfig.meta.dbAlias,
'migrations'
);
if (!fs.existsSync(migrationFolder)) { if (!fs.existsSync(migrationFolder)) {
await migrator.init({ await migrator.init({
folder: this.config?.toolDir, folder: this.config?.toolDir,
@ -656,7 +717,6 @@ export default class NcProjectBuilder {
sqlContentMigrate: 1, sqlContentMigrate: 1,
sqlClient sqlClient
}); });
} catch (e) { } catch (e) {
console.log(e); console.log(e);
// throw e; // throw e;
@ -666,44 +726,47 @@ export default class NcProjectBuilder {
} }
} }
protected static triggerGarbageCollect() { protected static triggerGarbageCollect() {
try { try {
if (global.gc) { if (global.gc) {
global.gc(); global.gc();
} }
} catch (e) { } catch (e) {
console.log("`node --expose-gc index.js`"); console.log('`node --expose-gc index.js`');
process.exit(); process.exit();
} }
} }
protected initApiInfoRoute(): void { protected initApiInfoRoute(): void {
this.router.get(`/projectApiInfo`, (req: any, res): any => { this.router.get(`/projectApiInfo`, (req: any, res): any => {
// auth to admin // auth to admin
if (this.config.auth) { if (this.config.auth) {
if (this.config.auth.jwt) { if (this.config.auth.jwt) {
if (!(req.session.passport.user.roles.creator if (
|| req.session.passport.user.roles.editor !(
|| req.session.passport.user.roles.commenter req.session.passport.user.roles.creator ||
|| req.session.passport.user.roles.viewer)) { req.session.passport.user.roles.editor ||
req.session.passport.user.roles.commenter ||
req.session.passport.user.roles.viewer
)
) {
return res.status(401).json({ return res.status(401).json({
msg: 'Unauthorized access : xc-auth does not have admin permission' msg:
}) 'Unauthorized access : xc-auth does not have admin permission'
});
} }
} else if (this.config.auth.masterKey) { } else if (this.config.auth.masterKey) {
if (req.headers['xc-master-key'] !== this.config.auth.masterKey.secret) { if (
req.headers['xc-master-key'] !== this.config.auth.masterKey.secret
) {
return res.status(401).json({ return res.status(401).json({
msg: 'Unauthorized access : xc-admin header missing or not matching' msg:
}) 'Unauthorized access : xc-admin header missing or not matching'
});
} }
} }
} }
const info: any = {}; const info: any = {};
for (const builder of this.apiBuilders) { for (const builder of this.apiBuilders) {
@ -713,8 +776,8 @@ export default class NcProjectBuilder {
gqlApiUrl: `/nc/${this.id}/${builder.apiPrefix}/graphql`, gqlApiUrl: `/nc/${this.id}/${builder.apiPrefix}/graphql`,
grpcApiUrl: ``, grpcApiUrl: ``,
apiType: builder.apiType, apiType: builder.apiType,
database: builder.getDbName(), database: builder.getDbName()
} };
} }
const result = { const result = {
@ -723,62 +786,64 @@ export default class NcProjectBuilder {
list: this.apiInfInfoList, list: this.apiInfInfoList,
aggregated: this.aggregatedApiInfo aggregated: this.aggregatedApiInfo
} }
} };
res.json(result); res.json(result);
}); });
} }
protected async progress(info, allInfo, isFirstTime?) { protected async progress(info, allInfo, isFirstTime?) {
const aggregatedInfo = allInfo
const aggregatedInfo = allInfo.reduce((arrSum, infoObj) => [ .reduce(
'', (arrSum, infoObj) => [
arrSum[1] + +infoObj.tablesCount, '',
arrSum[1] + +infoObj.tablesCount,
arrSum[2] + (infoObj.type === 'graphql' ? 1 : 0),
arrSum[3] + +(infoObj.type === 'rest' ? 1 : 0), arrSum[2] + (infoObj.type === 'graphql' ? 1 : 0),
arrSum[3] + +(infoObj.type === 'rest' ? 1 : 0),
arrSum[4] + (+infoObj.apiCount || +infoObj.resolversCount || 0),
// arrSum[3] + +info.timeTaken arrSum[4] + (+infoObj.apiCount || +infoObj.resolversCount || 0),
(Date.now() - this.startTime) / 1000 // arrSum[3] + +info.timeTaken
], (Date.now() - this.startTime) / 1000
[0, 0, 0, 0, 0, 0]) ],
.map((v, i) => (i === 5 ? v.toFixed(1) + 's' : (i === 2 ? v.toLocaleString() : v))); [0, 0, 0, 0, 0, 0]
)
.map((v, i) =>
i === 5 ? v.toFixed(1) + 's' : i === 2 ? v.toLocaleString() : v
);
this.apiInfInfoList.push(info); this.apiInfInfoList.push(info);
this.aggregatedApiInfo = aggregatedInfo; this.aggregatedApiInfo = aggregatedInfo;
if (isFirstTime) { if (isFirstTime) {
Tele.emit('evt_api_created', info); Tele.emit('evt_api_created', info);
} }
} }
protected async addAuthHookToMiddleware(): Promise<any> { protected async addAuthHookToMiddleware(): Promise<any> {
this.authHook = await this.app.ncMeta.metaGet(this.id, 'db', 'nc_hooks', { this.authHook = await this.app.ncMeta.metaGet(this.id, 'db', 'nc_hooks', {
type: 'AUTH_MIDDLEWARE' type: 'AUTH_MIDDLEWARE'
}); });
this.router.use(async (req: any, _res, next) => { this.router.use(async (req: any, _res, next) => {
if (this.authHook && this.authHook.url) { if (this.authHook && this.authHook.url) {
try { try {
const result = await axios.post(this.authHook.url, {}, { const result = await axios.post(
headers: req.headers this.authHook.url,
}); {},
{
headers: req.headers
}
);
req.locals = req.locals || {}; req.locals = req.locals || {};
req.locals = {user: result.data}; req.locals = { user: result.data };
} catch (e) { } catch (e) {
console.log(e) console.log(e);
} }
} }
next(); next();
}) });
} }
public get prefix(): string { public get prefix(): string {
return this.config?.prefix; return this.config?.prefix;
} }
@ -788,14 +853,14 @@ export default class NcProjectBuilder {
} }
public updateConfig(config: string) { public updateConfig(config: string) {
this._config = {...this.appConfig, ...JSON.parse(config)}; this._config = { ...this.appConfig, ...JSON.parse(config) };
} }
public async reInit() { public async reInit() {
this.router.stack.splice(0, this.router.stack.length); this.router.stack.splice(0, this.router.stack.length);
this.apiBuilders.splice(0, this.apiBuilders.length); this.apiBuilders.splice(0, this.apiBuilders.length);
await this.app.ncMeta.projectStatusUpdate(this.title, 'stopped'); await this.app.ncMeta.projectStatusUpdate(this.title, 'stopped');
const dbs = this.config?.envs?.[this.appConfig.workingEnv]?.db const dbs = this.config?.envs?.[this.appConfig.workingEnv]?.db;
if (!dbs || !dbs.length) { if (!dbs || !dbs.length) {
return; return;
@ -806,12 +871,11 @@ export default class NcProjectBuilder {
dbAlias: connectionConfig?.mets?.dbAlias, dbAlias: connectionConfig?.mets?.dbAlias,
env: this.config.env, env: this.config.env,
projectId: this.id projectId: this.id
}) });
} }
NcProjectBuilder.triggerGarbageCollect(); NcProjectBuilder.triggerGarbageCollect();
await this.init(); await this.init();
} }
} }
/** /**

42
packages/nocodb/src/lib/noco/NcProjectBuilderEE.ts

@ -1,58 +1,62 @@
import NcProjectBuilder from "./NcProjectBuilder"; import NcProjectBuilder from './NcProjectBuilder';
export default class NcProjectBuilderEE extends NcProjectBuilder { export default class NcProjectBuilderEE extends NcProjectBuilder {
public async handleRunTimeChanges(data: any): Promise<any> { public async handleRunTimeChanges(data: any): Promise<any> {
const curBuilder = this.apiBuilders.find(builder => { const curBuilder = this.apiBuilders.find(builder => {
return (data.req?.dbAlias || data.req?.args?.dbAlias) === builder.getDbAlias(); return (
(data.req?.dbAlias || data.req?.args?.dbAlias) === builder.getDbAlias()
);
}); });
switch (data?.req?.api) { switch (data?.req?.api) {
case 'tableMetaRecreate': case 'tableMetaRecreate':
await curBuilder.onTableDelete(data.req.args.tn) await curBuilder.onTableDelete(data.req.args.tn);
await curBuilder.onTableCreate(data.req.args.tn, {}) await curBuilder.onTableCreate(data.req.args.tn, {});
break; break;
case 'viewMetaRecreate': case 'viewMetaRecreate':
await curBuilder.onViewDelete(data.req.args.tn) await curBuilder.onViewDelete(data.req.args.tn);
await curBuilder.onViewCreate(data.req.args.tn, {}) await curBuilder.onViewCreate(data.req.args.tn, {});
break; break;
case 'procedureMetaCreate': case 'procedureMetaCreate':
// todo: optimize // todo: optimize
if (Array.isArray(data.req.args.tableNames)) { if (Array.isArray(data.req.args.tableNames)) {
for (const procedure of data.req.args.tableNames) { for (const procedure of data.req.args.tableNames) {
await curBuilder.onProcedureCreate(procedure) await curBuilder.onProcedureCreate(procedure);
} }
} }
break; break;
case 'functionMetaRecreate': case 'functionMetaRecreate':
await curBuilder.onFunctionDelete(data.req.args.tn) await curBuilder.onFunctionDelete(data.req.args.tn);
await curBuilder.onFunctionCreate(data.req.args.tn) await curBuilder.onFunctionCreate(data.req.args.tn);
break; break;
case 'tableMetaCreate': case 'tableMetaCreate':
// await curBuilder.onTableCreate(data.req.args.tn) // await curBuilder.onTableCreate(data.req.args.tn)
await curBuilder.xcTablesPopulate({tableNames: data.req.args.tableNames.map(tn => ({tn})), type:'table'}); await curBuilder.xcTablesPopulate({
tableNames: data.req.args.tableNames.map(tn => ({ tn })),
type: 'table'
});
break; break;
case 'viewMetaCreate': case 'viewMetaCreate':
// await curBuilder.onTableCreate(data.req.args.tn) // await curBuilder.onTableCreate(data.req.args.tn)
await curBuilder.xcTablesPopulate({tableNames: data.req.args.viewNames, type: 'view'}); await curBuilder.xcTablesPopulate({
tableNames: data.req.args.viewNames,
type: 'view'
});
break; break;
case 'tableMetaDelete': case 'tableMetaDelete':
for (const table of data.req.args.tableNames) { for (const table of data.req.args.tableNames) {
await curBuilder.onTableDelete(table) await curBuilder.onTableDelete(table);
} }
break; break;
case 'viewMetaDelete': case 'viewMetaDelete':
for (const table of data.req.args.viewNames) { for (const table of data.req.args.viewNames) {
await curBuilder.onViewDelete(table) await curBuilder.onViewDelete(table);
} }
break; break;
case 'xcRelationsSet': case 'xcRelationsSet':
@ -60,11 +64,9 @@ export default class NcProjectBuilderEE extends NcProjectBuilder {
break; break;
default: default:
return super.handleRunTimeChanges(data) return super.handleRunTimeChanges(data);
} }
} }
} }
/** /**

306
packages/nocodb/src/lib/noco/Noco.ts

@ -1,47 +1,48 @@
/* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/ban-types */
import fs from "fs"; import fs from 'fs';
import path from 'path'; import path from 'path';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import bodyParser from "body-parser"; import bodyParser from 'body-parser';
import clear from 'clear'; import clear from 'clear';
import cookieParser from 'cookie-parser'; import cookieParser from 'cookie-parser';
import debug from 'debug'; import debug from 'debug';
import * as express from 'express' import * as express from 'express';
import {Router} from "express"; import { Router } from 'express';
import importFresh from "import-fresh"; import importFresh from 'import-fresh';
import morgan from "morgan"; import morgan from 'morgan';
import {Tele} from "nc-help"; import { Tele } from 'nc-help';
import NcToolGui from "nc-lib-gui"; import NcToolGui from 'nc-lib-gui';
import requestIp from 'request-ip'; import requestIp from 'request-ip';
import {v4 as uuidv4} from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import {NcConfig} from "../../interface/config"; import { NcConfig } from '../../interface/config';
import Migrator from '../migrator/SqlMigrator/lib/KnexMigrator'; import Migrator from '../migrator/SqlMigrator/lib/KnexMigrator';
import NcConfigFactory from "../utils/NcConfigFactory"; import NcConfigFactory from '../utils/NcConfigFactory';
import NcProjectBuilderCE from "./NcProjectBuilder"; import NcProjectBuilderCE from './NcProjectBuilder';
import NcProjectBuilderEE from "./NcProjectBuilderEE"; import NcProjectBuilderEE from './NcProjectBuilderEE';
import {GqlApiBuilder} from "./gql/GqlApiBuilder"; import { GqlApiBuilder } from './gql/GqlApiBuilder';
import NcMetaIO from "./meta/NcMetaIO"; import NcMetaIO from './meta/NcMetaIO';
import NcMetaImplCE from "./meta/NcMetaIOImpl"; import NcMetaImplCE from './meta/NcMetaIOImpl';
import NcMetaImplEE from "./meta/NcMetaIOImplEE"; import NcMetaImplEE from './meta/NcMetaIOImplEE';
import NcMetaMgrCE from "./meta/NcMetaMgr"; import NcMetaMgrCE from './meta/NcMetaMgr';
import NcMetaMgrEE from "./meta/NcMetaMgrEE"; import NcMetaMgrEE from './meta/NcMetaMgrEE';
import {RestApiBuilder} from "./rest/RestApiBuilder"; import { RestApiBuilder } from './rest/RestApiBuilder';
import RestAuthCtrlCE from "./rest/RestAuthCtrl"; import RestAuthCtrlCE from './rest/RestAuthCtrl';
import RestAuthCtrlEE from "./rest/RestAuthCtrlEE"; import RestAuthCtrlEE from './rest/RestAuthCtrlEE';
import mkdirp from 'mkdirp'; import mkdirp from 'mkdirp';
import MetaAPILogger from "./meta/MetaAPILogger"; import MetaAPILogger from './meta/MetaAPILogger';
import NcUpgrader from "./upgrader/NcUpgrader"; import NcUpgrader from './upgrader/NcUpgrader';
const log = debug('nc:app'); const log = debug('nc:app');
require('dotenv').config(); require('dotenv').config();
const NcProjectBuilder = process.env.EE ? NcProjectBuilderEE : NcProjectBuilderCE; const NcProjectBuilder = process.env.EE
? NcProjectBuilderEE
: NcProjectBuilderCE;
export default class Noco { export default class Noco {
private static _this: Noco; private static _this: Noco;
public static get dashboardUrl(): string { public static get dashboardUrl(): string {
@ -53,15 +54,15 @@ export default class Noco {
siteUrl = Noco._this?.config?.envs?.['_noco']?.publicUrl; siteUrl = Noco._this?.config?.envs?.['_noco']?.publicUrl;
} }
return `${siteUrl}${Noco._this?.config?.dashboardPath}` return `${siteUrl}${Noco._this?.config?.dashboardPath}`;
} }
public static async init(args?: { public static async init(args?: {
progressCallback?: Function, progressCallback?: Function;
registerRoutes?: Function, registerRoutes?: Function;
registerGql?: Function, registerGql?: Function;
registerContext?: Function, registerContext?: Function;
afterMetaMigrationInit?: Function afterMetaMigrationInit?: Function;
}): Promise<Router> { }): Promise<Router> {
if (Noco._this) { if (Noco._this) {
return Noco._this.router; return Noco._this.router;
@ -87,7 +88,6 @@ export default class Noco {
private socketClient: any; private socketClient: any;
constructor() { constructor() {
process.env.PORT = process.env.PORT || '8080'; process.env.PORT = process.env.PORT || '8080';
// todo: move // todo: move
process.env.NC_VERSION = '0011043'; process.env.NC_VERSION = '0011043';
@ -99,7 +99,7 @@ export default class Noco {
this.config = NcConfigFactory.make(); this.config = NcConfigFactory.make();
/******************* setup : start *******************/ /******************* setup : start *******************/
this.env = '_noco';//process.env['NODE_ENV'] || this.config.workingEnv || 'dev'; this.env = '_noco'; //process.env['NODE_ENV'] || this.config.workingEnv || 'dev';
this.config.workingEnv = this.env; this.config.workingEnv = this.env;
this.config.type = 'docker'; this.config.type = 'docker';
@ -136,19 +136,17 @@ export default class Noco {
// }); // });
clear(); clear();
/******************* prints : end *******************/ /******************* prints : end *******************/
} }
public async init(args?: { public async init(args?: {
progressCallback?: Function, progressCallback?: Function;
registerRoutes?: Function, registerRoutes?: Function;
registerGql?: Function, registerGql?: Function;
registerContext?: Function, registerContext?: Function;
afterMetaMigrationInit?: Function afterMetaMigrationInit?: Function;
}) { }) {
const { const {
progressCallback, progressCallback
// registerRoutes, // registerRoutes,
// registerContext, // registerContext,
// registerGql // registerGql
@ -156,7 +154,6 @@ export default class Noco {
log('Initializing app'); log('Initializing app');
// create tool directory if missing // create tool directory if missing
mkdirp.sync(this.config.toolDir); mkdirp.sync(this.config.toolDir);
@ -177,7 +174,7 @@ export default class Noco {
await this.readOrGenJwtSecret(); await this.readOrGenJwtSecret();
await NcUpgrader.upgrade({ncMeta: this.ncMeta}) await NcUpgrader.upgrade({ ncMeta: this.ncMeta });
if (args?.afterMetaMigrationInit) { if (args?.afterMetaMigrationInit) {
await args.afterMetaMigrationInit(); await args.afterMetaMigrationInit();
@ -186,26 +183,30 @@ export default class Noco {
/******************* Middlewares : start *******************/ /******************* Middlewares : start *******************/
this.router.use((req: any, _res, next) => { this.router.use((req: any, _res, next) => {
req.nc = this.requestContext; req.nc = this.requestContext;
req.ncSiteUrl = this.config?.envs?.[this.env]?.publicUrl || this.config?.publicUrl || (req.protocol + '://' + req.get('host')); req.ncSiteUrl =
this.config?.envs?.[this.env]?.publicUrl ||
this.config?.publicUrl ||
req.protocol + '://' + req.get('host');
req.ncFullUrl = req.protocol + '://' + req.get('host') + req.originalUrl; req.ncFullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
next(); next();
}); });
// to get ip addresses // to get ip addresses
this.router.use(requestIp.mw()) this.router.use(requestIp.mw());
this.router.use(cookieParser()); this.router.use(cookieParser());
this.router.use(bodyParser.json({ this.router.use(
limit: process.env.NC_REQUEST_BODY_SIZE || 1024 * 1024 bodyParser.json({
})); limit: process.env.NC_REQUEST_BODY_SIZE || 1024 * 1024
})
);
this.router.use(morgan('tiny')); this.router.use(morgan('tiny'));
this.router.use(express.static(path.join(__dirname, './public'))); this.router.use(express.static(path.join(__dirname, './public')));
this.router.use((req: any, _res, next) => { this.router.use((req: any, _res, next) => {
req.ncProjectId = req?.query?.project_id || req?.body?.project_id; req.ncProjectId = req?.query?.project_id || req?.body?.project_id;
next(); next();
}) });
/* this.router.use(this.config.dashboardPath, (req: any, _res, next) => { /* this.router.use(this.config.dashboardPath, (req: any, _res, next) => {
req.ncProjectId = req?.body?.project_id; req.ncProjectId = req?.body?.project_id;
next(); next();
@ -213,7 +214,7 @@ export default class Noco {
this.router.use(`/nc/:project_id/*`, (req: any, _res, next) => { this.router.use(`/nc/:project_id/*`, (req: any, _res, next) => {
req.ncProjectId = req.ncProjectId || req.params.project_id; req.ncProjectId = req.ncProjectId || req.params.project_id;
next(); next();
}) });
this.router.use(MetaAPILogger.mw); this.router.use(MetaAPILogger.mw);
/******************* Middlewares : end *******************/ /******************* Middlewares : end *******************/
@ -225,21 +226,25 @@ export default class Noco {
this.ncToolApi.addListener(runTimeHandler); this.ncToolApi.addListener(runTimeHandler);
this.metaMgr.setListener(runTimeHandler); this.metaMgr.setListener(runTimeHandler);
await this.metaMgr.initHandler(this.router); await this.metaMgr.initHandler(this.router);
this.router.use(this.config.dashboardPath, await this.ncToolApi.expressMiddleware()); this.router.use(
this.router.get('/', (_req, res) => res.redirect(this.config.dashboardPath)); this.config.dashboardPath,
await this.ncToolApi.expressMiddleware()
);
this.router.get('/', (_req, res) =>
res.redirect(this.config.dashboardPath)
);
this.initSentryErrorHandler(); this.initSentryErrorHandler();
/* catch error */ /* catch error */
this.router.use((err, _req, res, next) => { this.router.use((err, _req, res, next) => {
if (err) { if (err) {
return res.status(400).json({msg: err.message}); return res.status(400).json({ msg: err.message });
} }
next(); next();
}); });
Tele.emit('evt_app_started', {});
Tele.emit('evt_app_started', {})
return this.router; return this.router;
} }
@ -252,17 +257,14 @@ export default class Noco {
private initSentry() { private initSentry() {
if (process.env.NC_SENTRY_DSN) { if (process.env.NC_SENTRY_DSN) {
Sentry.init({dsn: process.env.NC_SENTRY_DSN}); Sentry.init({ dsn: process.env.NC_SENTRY_DSN });
// The request handler must be the first middleware on the app // The request handler must be the first middleware on the app
this.router.use(Sentry.Handlers.requestHandler()); this.router.use(Sentry.Handlers.requestHandler());
} }
} }
async initServerless() { async initServerless() {}
}
public getBuilders(): Array<RestApiBuilder | GqlApiBuilder> { public getBuilders(): Array<RestApiBuilder | GqlApiBuilder> {
return this.apiBuilders; return this.apiBuilders;
@ -276,128 +278,150 @@ export default class Noco {
this.requestContext = context; this.requestContext = context;
} }
private handleRuntimeChanges(_progressCallback: Function) { private handleRuntimeChanges(_progressCallback: Function) {
return async (data): Promise<any> => { return async (data): Promise<any> => {
switch (data?.req?.api) { switch (data?.req?.api) {
case 'projectCreateByWeb': case 'projectCreateByWeb':
case 'projectCreateByOneClick': case 'projectCreateByOneClick':
case 'projectCreateByWebWithXCDB': { case 'projectCreateByWebWithXCDB':
// || data?.req?.args?.project?.title || data?.req?.args?.title {
const project = await this.ncMeta.projectGetById(data?.res?.id) // || data?.req?.args?.project?.title || data?.req?.args?.title
const builder = new NcProjectBuilder(this, this.config, project); const project = await this.ncMeta.projectGetById(data?.res?.id);
this.projectBuilders.push(builder) const builder = new NcProjectBuilder(this, this.config, project);
await builder.init(true); this.projectBuilders.push(builder);
} await builder.init(true);
}
break; break;
// create project builder for newly imported project // create project builder for newly imported project
// duplicated code - projectCreateByWeb // duplicated code - projectCreateByWeb
case 'xcMetaTablesImportZipToLocalFsAndDb': { case 'xcMetaTablesImportZipToLocalFsAndDb':
if (data.req?.freshImport) { {
const project = await this.ncMeta.projectGetById(data?.req?.project_id) if (data.req?.freshImport) {
const builder = new NcProjectBuilder(this, this.config, project); const project = await this.ncMeta.projectGetById(
this.projectBuilders.push(builder) data?.req?.project_id
await builder.init(true); );
} else { const builder = new NcProjectBuilder(this, this.config, project);
const projectBuilder = this.projectBuilders.find(pb => pb.id == data.req?.project_id); this.projectBuilders.push(builder);
return projectBuilder?.handleRunTimeChanges(data); await builder.init(true);
} else {
const projectBuilder = this.projectBuilders.find(
pb => pb.id == data.req?.project_id
);
return projectBuilder?.handleRunTimeChanges(data);
}
} }
}
break; break;
case 'projectUpdateByWeb': { case 'projectUpdateByWeb':
const projectId = data.req?.project_id; {
const project = await this.ncMeta.projectGetById(data?.req?.project_id) const projectId = data.req?.project_id;
const projectBuilder = this.projectBuilders.find(pb => pb.id === projectId); const project = await this.ncMeta.projectGetById(
data?.req?.project_id
projectBuilder.updateConfig(project.config) );
await projectBuilder.reInit() const projectBuilder = this.projectBuilders.find(
console.log(`Project updated: ${projectId}`) pb => pb.id === projectId
} );
projectBuilder.updateConfig(project.config);
await projectBuilder.reInit();
console.log(`Project updated: ${projectId}`);
}
break; break;
case 'projectChangeEnv': case 'projectChangeEnv':
try { try {
this.config = importFresh(path.join(process.cwd(), 'config.xc.json')) as NcConfig; this.config = importFresh(
path.join(process.cwd(), 'config.xc.json')
) as NcConfig;
this.config.toolDir = this.config.toolDir || process.cwd(); this.config.toolDir = this.config.toolDir || process.cwd();
this.ncMeta.setConfig(this.config); this.ncMeta.setConfig(this.config);
this.metaMgr.setConfig(this.config); this.metaMgr.setConfig(this.config);
Object.assign(process.env, {NODE_ENV: this.env = this.config.workingEnv}); Object.assign(process.env, {
NODE_ENV: this.env = this.config.workingEnv
});
this.router.stack.splice(0, this.router.stack.length); this.router.stack.splice(0, this.router.stack.length);
this.ncToolApi.destroy(); this.ncToolApi.destroy();
this.ncToolApi.reInitialize(this.config); this.ncToolApi.reInitialize(this.config);
// await this.init({progressCallback}); // await this.init({progressCallback});
console.log(`Loaded env : ${data.req.args.env}`) console.log(`Loaded env : ${data.req.args.env}`);
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
break; break;
default: { default: {
const projectBuilder = this.projectBuilders.find(pb => pb.id == data.req?.project_id); const projectBuilder = this.projectBuilders.find(
pb => pb.id == data.req?.project_id
);
return projectBuilder?.handleRunTimeChanges(data); return projectBuilder?.handleRunTimeChanges(data);
} }
} }
}; };
} }
private async initProjectBuilders() { private async initProjectBuilders() {
const RestAuthCtrl = process.env.EE ? RestAuthCtrlEE : RestAuthCtrlCE; const RestAuthCtrl = process.env.EE ? RestAuthCtrlEE : RestAuthCtrlCE;
this.projectBuilders.splice(0, this.projectBuilders.length); this.projectBuilders.splice(0, this.projectBuilders.length);
await new RestAuthCtrl(this as any, await new RestAuthCtrl(
this as any,
this.ncMeta?.knex, this.ncMeta?.knex,
this.config?.meta?.db, this.config?.meta?.db,
this.config, this.ncMeta).init(); this.config,
this.ncMeta
).init();
this.router.use(this.projectRouter); this.router.use(this.projectRouter);
const projects = await this.ncMeta.projectList(); const projects = await this.ncMeta.projectList();
for (const project of projects) { for (const project of projects) {
const projectBuilder = new NcProjectBuilder(this, this.config, project); const projectBuilder = new NcProjectBuilder(this, this.config, project);
this.projectBuilders.push(projectBuilder) this.projectBuilders.push(projectBuilder);
} }
let i = 0; let i = 0;
for (const builder of this.projectBuilders) { for (const builder of this.projectBuilders) {
if (projects[i].status === 'started' || projects[i].status === 'starting') { if (
projects[i].status === 'started' ||
projects[i].status === 'starting'
) {
await builder.init(); await builder.init();
} }
i++; i++;
} }
} }
private async syncMigration(): Promise<void> { private async syncMigration(): Promise<void> {
if (
if (this.config?.toolDir this.config?.toolDir
// && !('NC_MIGRATIONS_DISABLED' in process.env) // && !('NC_MIGRATIONS_DISABLED' in process.env)
) { ) {
const dbs = this.config?.envs?.[this.env]?.db;
const dbs = this.config?.envs?.[this.env]?.db
if (!dbs || !dbs.length) { if (!dbs || !dbs.length) {
log(`'${this.env}' environment doesn't have any database configuration.`) log(
`'${this.env}' environment doesn't have any database configuration.`
);
return; return;
} }
for (const connectionConfig of dbs) { for (const connectionConfig of dbs) {
log(
log(`Migrations start >> ${connectionConfig?.connection?.['database']} (${connectionConfig.meta?.dbAlias})`) `Migrations start >> ${connectionConfig?.connection?.['database']} (${connectionConfig.meta?.dbAlias})`
);
try { try {
/* Update database migrations */ /* Update database migrations */
const migrator = new Migrator(); const migrator = new Migrator();
/* initialize migration if folder doesn't exist */ /* initialize migration if folder doesn't exist */
const migrationFolder = path.join(this.config.toolDir, 'server', 'tool', connectionConfig.meta.dbAlias, 'migrations'); const migrationFolder = path.join(
this.config.toolDir,
'server',
'tool',
connectionConfig.meta.dbAlias,
'migrations'
);
if (!fs.existsSync(migrationFolder)) { if (!fs.existsSync(migrationFolder)) {
await migrator.init({ await migrator.init({
folder: this.config?.toolDir, folder: this.config?.toolDir,
@ -417,38 +441,40 @@ export default class Noco {
env: this.env, env: this.env,
dbAlias: connectionConfig.meta.dbAlias, dbAlias: connectionConfig.meta.dbAlias,
migrationSteps: 99999, migrationSteps: 99999,
sqlContentMigrate: 1, sqlContentMigrate: 1
}); });
log(`Migrations end << ${connectionConfig?.connection?.['database']} (${connectionConfig.meta?.dbAlias})`) log(
`Migrations end << ${connectionConfig?.connection?.['database']} (${connectionConfig.meta?.dbAlias})`
);
} catch (e) { } catch (e) {
log(`Migrations Failed !! ${connectionConfig?.connection?.['database']} (${connectionConfig.meta?.dbAlias})`) log(
`Migrations Failed !! ${connectionConfig?.connection?.['database']} (${connectionConfig.meta?.dbAlias})`
);
console.log(e); console.log(e);
// throw e; // throw e;
} }
} }
} else { } else {
log('Warning : ignoring migrations on boot since tools directory not defined') log(
'Warning : ignoring migrations on boot since tools directory not defined'
);
} }
} }
private initWebSocket(): void { private initWebSocket(): void {
// todo: Auth // todo: Auth
this.router.get(`${this.config.dashboardPath}/demo`, (_req, res) => { this.router.get(`${this.config.dashboardPath}/demo`, (_req, res) => {
(this.ncMeta as any).updateKnex({ (this.ncMeta as any).updateKnex({
"client": "sqlite3", client: 'sqlite3',
"connection": { connection: {
"filename": "xcDemo.db" filename: 'xcDemo.db'
} }
}); });
res.json({msg: 'done'}); res.json({ msg: 'done' });
}) });
this.io = require('socket.io')(); this.io = require('socket.io')();
this.io.listen(8083); this.io.listen(8083);
@ -459,7 +485,6 @@ export default class Noco {
console.log('Disconnected'); console.log('Disconnected');
this.socketClient = null; this.socketClient = null;
}); });
}); });
const statusMonitor = require('express-status-monitor')({ const statusMonitor = require('express-status-monitor')({
@ -468,8 +493,10 @@ export default class Noco {
}); });
this.router.use(statusMonitor); this.router.use(statusMonitor);
this.router.get(`${this.config.dashboardPath}/status`, statusMonitor.pageRoute) this.router.get(
`${this.config.dashboardPath}/status`,
statusMonitor.pageRoute
);
/* /*
title: 'Express Status', // Default title title: 'Express Status', // Default title
@ -499,27 +526,24 @@ export default class Noco {
}, },
healthChecks: [], healthChecks: [],
ignoreStartsWith: '/admin'*/ ignoreStartsWith: '/admin'*/
} }
private async readOrGenJwtSecret(): Promise<any> { private async readOrGenJwtSecret(): Promise<any> {
if (this.config?.auth?.jwt && !this.config.auth.jwt.secret) { if (this.config?.auth?.jwt && !this.config.auth.jwt.secret) {
let secret = (await this.ncMeta.metaGet('', '', 'nc_store', { let secret = (
key: 'nc_auth_jwt_secret' await this.ncMeta.metaGet('', '', 'nc_store', {
}))?.value; key: 'nc_auth_jwt_secret'
})
)?.value;
if (!secret) { if (!secret) {
(await this.ncMeta.metaInsert('', '', 'nc_store', { await this.ncMeta.metaInsert('', '', 'nc_store', {
key: 'nc_auth_jwt_secret', key: 'nc_auth_jwt_secret',
value: secret = uuidv4() value: secret = uuidv4()
})) });
} }
this.config.auth.jwt.secret = secret; this.config.auth.jwt.secret = secret;
} }
} }
} }
/** /**

1724
packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts

File diff suppressed because it is too large Load Diff

17
packages/nocodb/src/lib/noco/common/BaseProcedure.ts

@ -1,16 +1,14 @@
import {GqlApiBuilder} from "../gql/GqlApiBuilder"; import { GqlApiBuilder } from '../gql/GqlApiBuilder';
import {RestApiBuilder} from "../rest/RestApiBuilder"; import { RestApiBuilder } from '../rest/RestApiBuilder';
import XcProcedure from "./XcProcedure"; import XcProcedure from './XcProcedure';
export default class BaseProcedure { export default class BaseProcedure {
protected builder: GqlApiBuilder | RestApiBuilder;
protected builder: GqlApiBuilder | RestApiBuilder;
protected procedures: any[]; protected procedures: any[];
protected functions: any[] protected functions: any[];
protected xcProcedure: XcProcedure; protected xcProcedure: XcProcedure;
public functionsSet(functions): void { public functionsSet(functions): void {
this.functions = functions; this.functions = functions;
} }
@ -30,17 +28,16 @@ export default class BaseProcedure {
public functionDelete(name: string): void { public functionDelete(name: string): void {
const index = this.functions.findIndex(f => f.function_name === name); const index = this.functions.findIndex(f => f.function_name === name);
if (index > -1) { if (index > -1) {
this.functions.splice(index, 1) this.functions.splice(index, 1);
} }
} }
public procedureDelete(name: string): void { public procedureDelete(name: string): void {
const index = this.procedures.findIndex(f => f.procedure_name === name); const index = this.procedures.findIndex(f => f.procedure_name === name);
if (index > -1) { if (index > -1) {
this.procedures.splice(index, 1) this.procedures.splice(index, 1);
} }
} }
} }
/** /**

138
packages/nocodb/src/lib/noco/common/NcConnectionMgr.ts

@ -1,19 +1,19 @@
import {XKnex} from "../../dataMapper"; import { XKnex } from '../../dataMapper';
import {NcConfig} from "../../../interface/config"; import { NcConfig } from '../../../interface/config';
import fs from "fs"; import fs from 'fs';
import Knex from "knex"; import Knex from 'knex';
import {SqlClientFactory} from 'nc-help'; import { SqlClientFactory } from 'nc-help';
import NcMetaIO from "../meta/NcMetaIO"; import NcMetaIO from '../meta/NcMetaIO';
import {defaultConnectionConfig} from "../../utils/NcConfigFactory"; import { defaultConnectionConfig } from '../../utils/NcConfigFactory';
export default class NcConnectionMgr { export default class NcConnectionMgr {
private static connectionRefs: { private static connectionRefs: {
[projectId: string]: { [projectId: string]: {
[env: string]: { [env: string]: {
[dbAlias: string]: XKnex [dbAlias: string]: XKnex;
} };
} };
} = {}; } = {};
private static metaKnex: NcMetaIO; private static metaKnex: NcMetaIO;
@ -23,13 +23,13 @@ export default class NcConnectionMgr {
} }
public static delete({ public static delete({
dbAlias = 'db', dbAlias = 'db',
env = '_noco', env = '_noco',
projectId projectId
}: { }: {
dbAlias: string, dbAlias: string;
env: string, env: string;
projectId: string projectId: string;
}) { }) {
// todo: ignore meta projects // todo: ignore meta projects
if (this.connectionRefs?.[projectId]?.[env]?.[dbAlias]) { if (this.connectionRefs?.[projectId]?.[env]?.[dbAlias]) {
@ -44,27 +44,31 @@ export default class NcConnectionMgr {
} }
public static get({ public static get({
dbAlias = 'db', dbAlias = 'db',
env = '_noco', env = '_noco',
config, config,
projectId projectId
}: { }: {
dbAlias: string, dbAlias: string;
env: string, env: string;
config: NcConfig, config: NcConfig;
projectId: string projectId: string;
}): XKnex { }): XKnex {
if (this.connectionRefs?.[projectId]?.[env]?.[dbAlias]) { if (this.connectionRefs?.[projectId]?.[env]?.[dbAlias]) {
return this.connectionRefs?.[projectId]?.[env]?.[dbAlias]; return this.connectionRefs?.[projectId]?.[env]?.[dbAlias];
} }
this.connectionRefs[projectId] = this.connectionRefs[projectId] || {}; this.connectionRefs[projectId] = this.connectionRefs[projectId] || {};
this.connectionRefs[projectId][env] = this.connectionRefs[projectId] [env] || {}; this.connectionRefs[projectId][env] =
this.connectionRefs[projectId][env] || {};
if (config?.prefix && this.metaKnex) { if (config?.prefix && this.metaKnex) {
this.connectionRefs[projectId][env][dbAlias] = this.metaKnex?.knex; this.connectionRefs[projectId][env][dbAlias] = this.metaKnex?.knex;
} else { } else {
const connectionConfig = this.getConnectionConfig(config, env, dbAlias) const connectionConfig = this.getConnectionConfig(config, env, dbAlias);
if (connectionConfig?.connection?.ssl && typeof connectionConfig?.connection?.ssl === 'object') { if (
connectionConfig?.connection?.ssl &&
typeof connectionConfig?.connection?.ssl === 'object'
) {
if (connectionConfig.connection.ssl.caFilePath) { if (connectionConfig.connection.ssl.caFilePath) {
connectionConfig.connection.ssl.ca = fs connectionConfig.connection.ssl.ca = fs
.readFileSync(connectionConfig.connection.ssl.caFilePath) .readFileSync(connectionConfig.connection.ssl.caFilePath)
@ -85,58 +89,66 @@ export default class NcConnectionMgr {
const isSqlite = connectionConfig?.client === 'sqlite3'; const isSqlite = connectionConfig?.client === 'sqlite3';
if (connectionConfig?.connection?.port) { if (connectionConfig?.connection?.port) {
connectionConfig.connection.port = +connectionConfig.connection.port connectionConfig.connection.port = +connectionConfig.connection.port;
} }
this.connectionRefs[projectId][env][dbAlias] = XKnex(isSqlite ? this.connectionRefs[projectId][env][dbAlias] = XKnex(
connectionConfig.connection as Knex.Config : isSqlite
{ ? (connectionConfig.connection as Knex.Config)
...connectionConfig, : ({
connection: { ...connectionConfig,
...defaultConnectionConfig, connection: {
...connectionConfig.connection, ...defaultConnectionConfig,
typeCast(_field, next) { ...connectionConfig.connection,
const res = next(); typeCast(_field, next) {
if (res instanceof Buffer) { const res = next();
return [...res].map(v => ('00' + v.toString(16)).slice(-2)).join(''); if (res instanceof Buffer) {
return [...res]
.map(v => ('00' + v.toString(16)).slice(-2))
.join('');
}
return res;
}
} }
return res; } as any)
} );
}
} as any);
if (isSqlite) { if (isSqlite) {
this.connectionRefs[projectId][env][dbAlias].raw(`PRAGMA journal_mode=WAL;`).then(() => { this.connectionRefs[projectId][env][dbAlias]
}) .raw(`PRAGMA journal_mode=WAL;`)
.then(() => {});
} }
} }
return this.connectionRefs[projectId][env][dbAlias]; return this.connectionRefs[projectId][env][dbAlias];
} }
private static getConnectionConfig(
private static getConnectionConfig(config: NcConfig, env: string, dbAlias: string) { config: NcConfig,
env: string,
dbAlias: string
) {
return config?.envs?.[env]?.db?.find(db => db?.meta?.dbAlias === dbAlias); return config?.envs?.[env]?.db?.find(db => db?.meta?.dbAlias === dbAlias);
} }
public static getSqlClient({ public static getSqlClient({
projectId, projectId,
dbAlias = 'db', dbAlias = 'db',
env = '_noco', env = '_noco',
config config
}: { }: {
dbAlias: string, dbAlias: string;
env: string, env: string;
config: NcConfig, config: NcConfig;
projectId: string projectId: string;
}): any { }): any {
const knex = this.get({ const knex = this.get({
dbAlias, dbAlias,
env, env,
config, config,
projectId projectId
}) });
return SqlClientFactory.create({knex, ...this.getConnectionConfig(config, env, dbAlias)}) return SqlClientFactory.create({
knex,
...this.getConnectionConfig(config, env, dbAlias)
});
} }
} }

19
packages/nocodb/src/lib/noco/common/XcAudit.ts

@ -1,20 +1,13 @@
import Noco from "../Noco"; import Noco from '../Noco';
export default class XcAudit{ export default class XcAudit {
public static init(app: Noco) {
public static init(app:Noco){
this.app = app; this.app = app;
} }
// @ts-ignore // @ts-ignore
private static app:Noco; private static app: Noco;
// @ts-ignore // @ts-ignore
public static async log(data:{ public static async log(data: { project }) {}
project }
}){
}
}

33
packages/nocodb/src/lib/noco/common/XcCron.ts

@ -1,15 +1,13 @@
import {CronJob} from 'cron'; import { CronJob } from 'cron';
import {NcConfig} from "../../../interface/config"; import { NcConfig } from '../../../interface/config';
import Noco from "../Noco"; import Noco from '../Noco';
import BaseApiBuilder from "./BaseApiBuilder"; import BaseApiBuilder from './BaseApiBuilder';
// import * as tsc from "typescript"; // import * as tsc from "typescript";
export class XcCron { export class XcCron {
// @ts-ignore // @ts-ignore
private app: Noco; private app: Noco;
// @ts-ignore // @ts-ignore
@ -17,7 +15,6 @@ export class XcCron {
private apiBuilder: BaseApiBuilder<Noco>; private apiBuilder: BaseApiBuilder<Noco>;
private cronJobs: { [key: string]: CronJob }; private cronJobs: { [key: string]: CronJob };
constructor(config: NcConfig, apiBuilder: BaseApiBuilder<Noco>, app: Noco) { constructor(config: NcConfig, apiBuilder: BaseApiBuilder<Noco>, app: Noco) {
this.app = app; this.app = app;
this.config = config; this.config = config;
@ -27,17 +24,20 @@ export class XcCron {
public async init(): Promise<any> { public async init(): Promise<any> {
// const cronJobs = await this.apiBuilder.getDbDriver()('nc_cron').select(); // const cronJobs = await this.apiBuilder.getDbDriver()('nc_cron').select();
const cronJobs = await this.apiBuilder.getXcMeta().metaList('', this.apiBuilder.dbAlias, 'nc_cron'); const cronJobs = await this.apiBuilder
.getXcMeta()
.metaList('', this.apiBuilder.dbAlias, 'nc_cron');
for (const cron of cronJobs) { for (const cron of cronJobs) {
this.startCronJob(cron); this.startCronJob(cron);
} }
} }
public async restartCron(args: any): Promise<any> { public async restartCron(args: any): Promise<any> {
// const cron = await this.apiBuilder.getDbDriver()('nc_cron').where('title', args.title).first(); // const cron = await this.apiBuilder.getDbDriver()('nc_cron').where('title', args.title).first();
const cron = await this.apiBuilder.getXcMeta().metaGet('', this.apiBuilder.dbAlias, 'nc_cron', {title: args.title}); const cron = await this.apiBuilder
.getXcMeta()
.metaGet('', this.apiBuilder.dbAlias, 'nc_cron', { title: args.title });
if (cron.id in this.cronJobs) { if (cron.id in this.cronJobs) {
this.cronJobs[cron.id].stop(); this.cronJobs[cron.id].stop();
@ -52,10 +52,9 @@ export class XcCron {
} }
} }
private startCronJob(cron): void { private startCronJob(cron): void {
if (!cron.active) { if (!cron.active) {
return return;
} }
try { try {
const job = new CronJob( const job = new CronJob(
@ -68,13 +67,11 @@ export class XcCron {
job.start(); job.start();
this.cronJobs[cron.id] = job; this.cronJobs[cron.id] = job;
} catch (e) { } catch (e) {
console.log('Error in cron initialization : ', e.message) console.log('Error in cron initialization : ', e.message);
} }
} }
private generateCronHandlerFromStringBody(fnBody: string): any { private generateCronHandlerFromStringBody(fnBody: string): any {
// @ts-ignore // @ts-ignore
let handler = () => { let handler = () => {
console.log('Empty handler'); console.log('Empty handler');
@ -95,16 +92,16 @@ export class XcCron {
// tslint:disable-next-line:no-eval // tslint:disable-next-line:no-eval
handler = eval(js); handler = eval(js);
// console.timeEnd('startTrans') // console.timeEnd('startTrans')
} catch (e) { } catch (e) {
console.log('Error in Cron handler transpilation', e) console.log('Error in Cron handler transpilation', e);
} }
// tslint:disable-next-line:no-eval // tslint:disable-next-line:no-eval
} }
return handler; return handler;
} }
}/** }
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>

53
packages/nocodb/src/lib/noco/common/XcProcedure.ts

@ -1,4 +1,4 @@
import BaseApiBuilder from "./BaseApiBuilder"; import BaseApiBuilder from './BaseApiBuilder';
export default class XcProcedure { export default class XcProcedure {
private builder: BaseApiBuilder<any>; private builder: BaseApiBuilder<any>;
@ -10,10 +10,22 @@ export default class XcProcedure {
public async callFunction(name: string, args: any[]) { public async callFunction(name: string, args: any[]) {
try { try {
if (this.builder.getDbType() === 'mssql') { if (this.builder.getDbType() === 'mssql') {
const result = await this.builder.getDbDriver().raw(`select dbo.??(${new Array(args.length).fill('?').join(',')}) as ??`, [name, ...args, name]); const result = await this.builder
.getDbDriver()
.raw(
`select dbo.??(${new Array(args.length)
.fill('?')
.join(',')}) as ??`,
[name, ...args, name]
);
return result[0]; return result[0];
} else { } else {
const result = await this.builder.getDbDriver().raw(`select ??(${new Array(args.length).fill('?').join(',')}) as ??`, [name, ...args, name]); const result = await this.builder
.getDbDriver()
.raw(
`select ??(${new Array(args.length).fill('?').join(',')}) as ??`,
[name, ...args, name]
);
return result[0]; return result[0];
} }
} catch (e) { } catch (e) {
@ -24,7 +36,7 @@ export default class XcProcedure {
public async callProcedure(name: string, args: any[]) { public async callProcedure(name: string, args: any[]) {
try { try {
if (this.builder.getDbType() === 'mssql') { if (this.builder.getDbType() === 'mssql') {
throw new Error('Not implemented') throw new Error('Not implemented');
/* /*
const sql = require('mssql'); const sql = require('mssql');
const request = new sql.Request({ const request = new sql.Request({
@ -47,22 +59,39 @@ export default class XcProcedure {
// const result = '' // mcnd await this.builder.getDbDriver().raw(`Call ??(${Array.from({length: count}, (_, i) => '@var' + i).join(',')})`, [name]); // const result = '' // mcnd await this.builder.getDbDriver().raw(`Call ??(${Array.from({length: count}, (_, i) => '@var' + i).join(',')})`, [name]);
// //
// return result) // return result)
} else if (this.builder.getDbType() === 'mysql2' || this.builder.getDbType() === 'mysql') { } else if (
const knexRef = args.reduce((knex, val, i) => knex.raw(`SET @var${i}=?`, [val]), this.builder.getDbDriver().schema) this.builder.getDbType() === 'mysql2' ||
const count = args.length this.builder.getDbType() === 'mysql'
const result = await knexRef.raw(`Call ??(${Array.from({length: count}, (_, i) => '@var' + i).join(',')})`, [name]); ) {
const knexRef = args.reduce(
(knex, val, i) => knex.raw(`SET @var${i}=?`, [val]),
this.builder.getDbDriver().schema
);
const count = args.length;
const result = await knexRef.raw(
`Call ??(${Array.from({ length: count }, (_, i) => '@var' + i).join(
','
)})`,
[name]
);
return [result[count][0][0]]; return [result[count][0][0]];
} else if (this.builder.getDbType() === 'pg') { } else if (this.builder.getDbType() === 'pg') {
const result = await this.builder.getDbDriver().raw(`Call ??(${new Array(args.length).fill('?').join(',')})`, [name, ...args]); const result = await this.builder
.getDbDriver()
.raw(`Call ??(${new Array(args.length).fill('?').join(',')})`, [
name,
...args
]);
return result; return result;
} else { } else {
throw new Error('Not implemented') throw new Error('Not implemented');
} }
} catch (e) { } catch (e) {
throw (e) throw e;
} }
} }
}/** }
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>

2
packages/nocodb/src/lib/noco/common/formSubmissionEmailTemplate.ts

@ -193,7 +193,7 @@ export default `<!doctype html>
</table> </table>
</body> </body>
</html> </html>
` `;
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd

19
packages/nocodb/src/lib/noco/common/helpers/addErrorOnColumnDeleteInFormula.ts

@ -1,18 +1,19 @@
export default function (args: { export default function(args: {
virtualColumns, virtualColumns;
columnName: string columnName: string;
}): void | boolean { }): void | boolean {
let modified = false; let modified = false;
const fn = (pt, virtualColumn) => { const fn = (pt, virtualColumn) => {
if (pt.type === 'CallExpression') { if (pt.type === 'CallExpression') {
pt.arguments.map(arg => fn(arg, virtualColumn)) pt.arguments.map(arg => fn(arg, virtualColumn));
} else if (pt.type === 'Literal') { } else if (pt.type === 'Literal') {
} else if (pt.type === 'Identifier') { } else if (pt.type === 'Identifier') {
if (pt.name === args.columnName) { if (pt.name === args.columnName) {
virtualColumn.formula.error = virtualColumn.formula.error || []; virtualColumn.formula.error = virtualColumn.formula.error || [];
virtualColumn.formula.error.push(`Column '${args.columnName}' was deleted`) virtualColumn.formula.error.push(
`Column '${args.columnName}' was deleted`
);
modified = true; modified = true;
} }
} else if (pt.type === 'BinaryExpression') { } else if (pt.type === 'BinaryExpression') {
@ -22,13 +23,13 @@ export default function (args: {
}; };
if (!args.virtualColumns) { if (!args.virtualColumns) {
return return;
} }
for (const v of args.virtualColumns) { for (const v of args.virtualColumns) {
if (!v.formula?.tree) { if (!v.formula?.tree) {
continue; continue;
} }
fn(v.formula.tree, v) fn(v.formula.tree, v);
} }
return modified; return modified;
} }

48
packages/nocodb/src/lib/noco/common/helpers/jsepTreeToFormula.ts

@ -1,43 +1,67 @@
export default function jsepTreeToFormula(node) { export default function jsepTreeToFormula(node) {
if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') { if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') {
return '(' + jsepTreeToFormula(node.left) + ' ' + node.operator + ' ' + jsepTreeToFormula(node.right) + ')' return (
'(' +
jsepTreeToFormula(node.left) +
' ' +
node.operator +
' ' +
jsepTreeToFormula(node.right) +
')'
);
} }
if (node.type === 'UnaryExpression') { if (node.type === 'UnaryExpression') {
return node.operator + jsepTreeToFormula(node.argument) return node.operator + jsepTreeToFormula(node.argument);
} }
if (node.type === 'MemberExpression') { if (node.type === 'MemberExpression') {
return jsepTreeToFormula(node.object) + '[' + jsepTreeToFormula(node.property) + ']' return (
jsepTreeToFormula(node.object) +
'[' +
jsepTreeToFormula(node.property) +
']'
);
} }
if (node.type === 'Identifier') { if (node.type === 'Identifier') {
return node.name return node.name;
} }
if (node.type === 'Literal') { if (node.type === 'Literal') {
if (typeof node.value === 'string') { if (typeof node.value === 'string') {
return '"' + node.value + '"' return '"' + node.value + '"';
} }
return '' + node.value return '' + node.value;
} }
if (node.type === 'CallExpression') { if (node.type === 'CallExpression') {
return jsepTreeToFormula(node.callee) + '(' + node.arguments.map(jsepTreeToFormula).join(', ') + ')' return (
jsepTreeToFormula(node.callee) +
'(' +
node.arguments.map(jsepTreeToFormula).join(', ') +
')'
);
} }
if (node.type === 'ArrayExpression') { if (node.type === 'ArrayExpression') {
return '[' + node.elements.map(jsepTreeToFormula).join(', ') + ']' return '[' + node.elements.map(jsepTreeToFormula).join(', ') + ']';
} }
if (node.type === 'Compound') { if (node.type === 'Compound') {
return node.body.map(e => jsepTreeToFormula(e)).join(' ') return node.body.map(e => jsepTreeToFormula(e)).join(' ');
} }
if (node.type === 'ConditionalExpression') { if (node.type === 'ConditionalExpression') {
return jsepTreeToFormula(node.test) + ' ? ' + jsepTreeToFormula(node.consequent) + ' : ' + jsepTreeToFormula(node.alternate) return (
jsepTreeToFormula(node.test) +
' ? ' +
jsepTreeToFormula(node.consequent) +
' : ' +
jsepTreeToFormula(node.alternate)
);
} }
return '' return '';
} }

23
packages/nocodb/src/lib/noco/common/helpers/updateColumnNameInFormula.ts

@ -1,16 +1,15 @@
import jsepTreeToFormula from "./jsepTreeToFormula"; import jsepTreeToFormula from './jsepTreeToFormula';
export default function (args: { export default function(args: {
virtualColumns, virtualColumns;
oldColumnName: string, oldColumnName: string;
newColumnName: string, newColumnName: string;
}): void | boolean { }): void | boolean {
let modified = false; let modified = false;
const fn = (pt) => { const fn = pt => {
if (pt.type === 'CallExpression') { if (pt.type === 'CallExpression') {
pt.arguments.map(arg => fn(arg)) pt.arguments.map(arg => fn(arg));
} else if (pt.type === 'Literal') { } else if (pt.type === 'Literal') {
} else if (pt.type === 'Identifier') { } else if (pt.type === 'Identifier') {
if (pt.name === args.oldColumnName) { if (pt.name === args.oldColumnName) {
@ -24,14 +23,14 @@ export default function (args: {
}; };
if (!args.virtualColumns) { if (!args.virtualColumns) {
return return;
} }
for (const v of args.virtualColumns) { for (const v of args.virtualColumns) {
if (!v.formula?.tree) { if (!v.formula?.tree) {
continue; continue;
} }
fn(v.formula.tree) fn(v.formula.tree);
v.formula.value = jsepTreeToFormula(v.formula.tree) v.formula.value = jsepTreeToFormula(v.formula.tree);
} }
return modified; return modified;
} }

2503
packages/nocodb/src/lib/noco/gql/GqlApiBuilder.ts

File diff suppressed because it is too large Load Diff

411
packages/nocodb/src/lib/noco/gql/GqlAuthResolver.ts

@ -1,45 +1,46 @@
import {promisify} from 'util'; import { promisify } from 'util';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import * as ejs from 'ejs'; import * as ejs from 'ejs';
import * as jwt from 'jsonwebtoken'; import * as jwt from 'jsonwebtoken';
import passport from 'passport'; import passport from 'passport';
import {ExtractJwt, Strategy} from 'passport-jwt'; import { ExtractJwt, Strategy } from 'passport-jwt';
import IEmailAdapter from "../../../interface/IEmailAdapter"; import IEmailAdapter from '../../../interface/IEmailAdapter';
import {DbConfig, NcConfig} from "../../../interface/config"; import { DbConfig, NcConfig } from '../../../interface/config';
import {Knex, XKnex} from "../../dataMapper"; import { Knex, XKnex } from '../../dataMapper';
import Noco from "../Noco"; import Noco from '../Noco';
import authSchema from './auth/schema'; import authSchema from './auth/schema';
const {v4: uuidv4} = require('uuid'); const { v4: uuidv4 } = require('uuid');
const PassportLocalStrategy = require('passport-local').Strategy; const PassportLocalStrategy = require('passport-local').Strategy;
const autoBind = require('auto-bind'); const autoBind = require('auto-bind');
const {isEmail} = require('validator'); const { isEmail } = require('validator');
// import swaggerUi from 'swagger-ui-express'; // import swaggerUi from 'swagger-ui-express';
passport.serializeUser(function ({id, email, email_verified, roles, provider, firstname, lastname}, done) { passport.serializeUser(function(
{ id, email, email_verified, roles, provider, firstname, lastname },
done
) {
done(null, { done(null, {
id, id,
email, email,
email_verified, email_verified,
provider, provider,
firstname, lastname, firstname,
lastname,
roles: (roles || '') roles: (roles || '')
.split(',') .split(',')
.reduce((obj, role) => ({...obj, [role]: true}), {}) .reduce((obj, role) => ({ ...obj, [role]: true }), {})
}) });
;
}); });
passport.deserializeUser(function (user, done) { passport.deserializeUser(function(user, done) {
done(null, user); done(null, user);
}); });
export default class GqlAuthResolver { export default class GqlAuthResolver {
private app: Noco; private app: Noco;
private dbDriver: Knex; private dbDriver: Knex;
@ -48,58 +49,60 @@ export default class GqlAuthResolver {
private jwtOptions: any; private jwtOptions: any;
constructor(
constructor(app: Noco, dbDriver: XKnex, connectionConfig: DbConfig, config: NcConfig) { app: Noco,
dbDriver: XKnex,
connectionConfig: DbConfig,
config: NcConfig
) {
this.app = app; this.app = app;
this.dbDriver = dbDriver; this.dbDriver = dbDriver;
this.connectionConfig = connectionConfig; this.connectionConfig = connectionConfig;
this.config = config; this.config = config;
autoBind(this); autoBind(this);
this.jwtOptions = {} this.jwtOptions = {};
this.jwtOptions.jwtFromRequest = ExtractJwt.fromHeader('xc-auth'); this.jwtOptions.jwtFromRequest = ExtractJwt.fromHeader('xc-auth');
this.jwtOptions.secretOrKey = this.config?.auth?.jwt?.secret ?? 'secret'; this.jwtOptions.secretOrKey = this.config?.auth?.jwt?.secret ?? 'secret';
if (this.config?.auth?.jwt?.options) { if (this.config?.auth?.jwt?.options) {
Object.assign(this.jwtOptions, this.config?.auth?.jwt?.options); Object.assign(this.jwtOptions, this.config?.auth?.jwt?.options);
} }
} }
get users() { get users() {
return this.dbDriver('xc_users') return this.dbDriver('xc_users');
} }
public async init() { public async init() {
await this.emailClient?.init(); await this.emailClient?.init();
await this.createTableIfNotExist(); await this.createTableIfNotExist();
this.initStrategies(); this.initStrategies();
this.app.router.use(passport.initialize()) this.app.router.use(passport.initialize());
const apiPrefix = this.connectionConfig?.meta?.api?.prefix || 'v1'; const apiPrefix = this.connectionConfig?.meta?.api?.prefix || 'v1';
this.app.router.get('/password/reset/:token', function (req, res) { this.app.router.get('/password/reset/:token', function(req, res) {
res.render(__dirname + '/auth/resetPassword', { res.render(__dirname + '/auth/resetPassword', {
token: JSON.stringify(req.params?.token), token: JSON.stringify(req.params?.token),
baseUrl: `/api/${apiPrefix}/` baseUrl: `/api/${apiPrefix}/`
}); });
}); });
this.app.router.get('/email/verify/:token', function (req, res) { this.app.router.get('/email/verify/:token', function(req, res) {
res.render(__dirname + '/auth/emailVerify', { res.render(__dirname + '/auth/emailVerify', {
token: JSON.stringify(req.params?.token), token: JSON.stringify(req.params?.token),
baseUrl: `/api/${apiPrefix}/` baseUrl: `/api/${apiPrefix}/`
}); });
}); });
this.app.router.get('/signin', function (_req, res) { this.app.router.get('/signin', function(_req, res) {
res.render(__dirname + '/auth/signin', { res.render(__dirname + '/auth/signin', {
baseUrl: `/api/${apiPrefix}/` baseUrl: `/api/${apiPrefix}/`
}); });
}); });
this.app.router.get('/signup', function (_req, res) { this.app.router.get('/signup', function(_req, res) {
res.render(__dirname + '/auth/signup', { res.render(__dirname + '/auth/signup', {
baseUrl: `/api/${apiPrefix}/` baseUrl: `/api/${apiPrefix}/`
}); });
@ -107,65 +110,76 @@ export default class GqlAuthResolver {
this.app.router.use(async (req, res, next) => { this.app.router.use(async (req, res, next) => {
const user = await new Promise(resolve => { const user = await new Promise(resolve => {
passport.authenticate('jwt', {session: false}, (_err, user, _info) => { passport.authenticate(
if (user) { 'jwt',
return resolve(user); { session: false },
(_err, user, _info) => {
if (user) {
return resolve(user);
}
resolve({ roles: 'guest' });
} }
resolve({roles: 'guest'}) )(req, res, next);
})(req, res, next); });
})
await promisify((req as any).login.bind(req))(user); await promisify((req as any).login.bind(req))(user);
next(); next();
}); });
} }
public initStrategies() { public initStrategies() {
const self = this; const self = this;
passport.use(new Strategy(this.jwtOptions, (jwt_payload, done) => { passport.use(
this.users.where({ new Strategy(this.jwtOptions, (jwt_payload, done) => {
email: jwt_payload?.email this.users
}).first().then(user => { .where({
if (user) { email: jwt_payload?.email
return done(null, user); })
} else { .first()
return done(new Error('User not found')); .then(user => {
} if (user) {
}).catch(err => { return done(null, user);
return done(err); } else {
return done(new Error('User not found'));
}
})
.catch(err => {
return done(err);
});
}) })
})); );
passport.use(
passport.use(new PassportLocalStrategy({ new PassportLocalStrategy(
usernameField: 'email', {
session: false usernameField: 'email',
}, async function (email, password, done) { session: false
try { },
const user = await self.users.where({email}).first(); async function(email, password, done) {
if (!user) { try {
return done({msg: `Email ${email} is not registered!`}); const user = await self.users.where({ email }).first();
} if (!user) {
return done({ msg: `Email ${email} is not registered!` });
}
const hashedPassword = await promisify(bcrypt.hash)(password, user.salt); const hashedPassword = await promisify(bcrypt.hash)(
if (user.password !== hashedPassword) { password,
return done({msg: `Password not valid!`}); user.salt
} else { );
return done(null, user); if (user.password !== hashedPassword) {
return done({ msg: `Password not valid!` });
} else {
return done(null, user);
}
} catch (e) {
done(e);
} }
} catch (e) {
done(e);
} }
} )
)); );
} }
public getResolvers() { public getResolvers() {
return { return {
mapResolvers: () => ({ mapResolvers: () => ({
@ -178,65 +192,68 @@ export default class GqlAuthResolver {
TokenVerify: this.tokenValidate, TokenVerify: this.tokenValidate,
ChangePassword: this.passwordChange ChangePassword: this.passwordChange
}) })
} };
} }
public getSchema() { public getSchema() {
return authSchema; return authSchema;
} }
public async signin(args, { req, res, next }) {
public async signin(args, {req, res, next}) {
req.body = args.data; req.body = args.data;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
passport.authenticate('local', {session: false}, async (err, user, info): Promise<any> => { passport.authenticate(
'local',
try { { session: false },
async (err, user, info): Promise<any> => {
if (!user || !user.email) { try {
if (err) { if (!user || !user.email) {
return reject(err) if (err) {
} return reject(err);
if (info) { }
return reject(info) if (info) {
return reject(info);
}
return reject({ msg: 'Your signin has failed' });
} }
return reject({msg: 'Your signin has failed'});
}
await promisify((req as any).login.bind(req))(user);
resolve({ await promisify((req as any).login.bind(req))(user);
token: jwt.sign({
email: user.email, resolve({
firstname: user.firstname, token: jwt.sign(
lastname: user.lastname, {
id: user.id, email: user.email,
roles: user.roles firstname: user.firstname,
}, this.jwtOptions.secretOrKey, this.config?.auth?.jwt?.options) lastname: user.lastname,
}); id: user.id,
} catch (e) { roles: user.roles
console.log(e); },
reject(e); this.jwtOptions.secretOrKey,
this.config?.auth?.jwt?.options
)
});
} catch (e) {
console.log(e);
reject(e);
}
} }
)(req, res, next);
})(req, res, next);
}); });
} }
public async signup(args, {req}) { public async signup(args, { req }) {
const { email, firstname, lastname } = args.data;
let { password } = args.data;
const {email, firstname, lastname} = args.data;
let {password} = args.data;
if (!isEmail(email)) { if (!isEmail(email)) {
throw new Error(`Not a valid email`); throw new Error(`Not a valid email`);
} }
let user = await this.users.where({ let user = await this.users
email .where({
}).first(); email
})
.first();
if (user) { if (user) {
throw new Error(`Email '${email}' already registered`); throw new Error(`Email '${email}' already registered`);
@ -251,90 +268,102 @@ export default class GqlAuthResolver {
const email_verification_token = uuidv4(); const email_verification_token = uuidv4();
await this.users.insert({ await this.users.insert({
firstname, lastname, firstname,
lastname,
email, email,
salt, salt,
password, password,
email_verification_token email_verification_token
}); });
user = await this.users.where({ user = await this.users
email .where({
}).first(); email
})
.first();
try { try {
const template = (await import('./emailTemplate/verify')).default; const template = (await import('./emailTemplate/verify')).default;
await this.emailClient.mailSend({ await this.emailClient.mailSend({
to: email, to: email,
subject: "Verify email", subject: 'Verify email',
html: ejs.render(template, { html: ejs.render(template, {
verifyLink: `${req.ncSiteUrl}/email/verify/${user.email_verification_token}` verifyLink: `${req.ncSiteUrl}/email/verify/${user.email_verification_token}`
}) })
}) });
} catch (e) { } catch (e) {
console.log('Warning : `mailSend` failed, Please configure email configuration.'); console.log(
'Warning : `mailSend` failed, Please configure email configuration.'
);
} }
await promisify((req as any).login.bind(req))(user); await promisify((req as any).login.bind(req))(user);
user = (req as any).user; user = (req as any).user;
return { return {
token: jwt.sign({ token: jwt.sign(
email: user.email, {
firstname: user.firstname, email: user.email,
lastname: user.lastname, firstname: user.firstname,
id: user.id, lastname: user.lastname,
roles: user.roles id: user.id,
}, this.jwtOptions.secretOrKey, this.config?.auth?.jwt?.options) roles: user.roles
},
this.jwtOptions.secretOrKey,
this.config?.auth?.jwt?.options
)
}; };
} }
public async passwordForgot(args, {req}) { public async passwordForgot(args, { req }) {
const email = args.email; const email = args.email;
if (!email) { if (!email) {
throw (new Error('Please enter your email address.')); throw new Error('Please enter your email address.');
} }
const user = await this.users.where({email}).first(); const user = await this.users.where({ email }).first();
if (!user) { if (!user) {
throw (new Error('This email is not registered with us.')); throw new Error('This email is not registered with us.');
} }
const token = uuidv4(); const token = uuidv4();
await this.users.update({ await this.users
reset_password_token: token, .update({
reset_password_expires: new Date(Date.now() + (60 * 60 * 1000)) reset_password_token: token,
}).where({id: user.id}); reset_password_expires: new Date(Date.now() + 60 * 60 * 1000)
})
.where({ id: user.id });
try { try {
const template = (await import('./emailTemplate/forgotPassword')).default; const template = (await import('./emailTemplate/forgotPassword')).default;
await this.emailClient.mailSend({ await this.emailClient.mailSend({
to: user.email, to: user.email,
subject: "Password Reset Link", subject: 'Password Reset Link',
text: `Visit following link to update your password : ${req.ncSiteUrl}/password/reset/${token}.`, text: `Visit following link to update your password : ${req.ncSiteUrl}/password/reset/${token}.`,
html: ejs.render(template, { html: ejs.render(template, {
resetLink: `${req.ncSiteUrl}/password/reset/${token}` resetLink: `${req.ncSiteUrl}/password/reset/${token}`
}) })
}) });
} catch (e) { } catch (e) {
console.log('Warning : `mailSend` failed, Please configure email configuration.'); console.log(
'Warning : `mailSend` failed, Please configure email configuration.'
);
} }
console.log(`Password reset token : ${token}`) console.log(`Password reset token : ${token}`);
return true; return true;
} }
public async tokenValidate(args) { public async tokenValidate(args) {
const token = args.tokenId; const token = args.tokenId;
const user = await this.users.where({reset_password_token: token}).first(); const user = await this.users
.where({ reset_password_token: token })
.first();
if (!user || !user.email) { if (!user || !user.email) {
throw new Error('Invalid token'); throw new Error('Invalid token');
} }
@ -345,97 +374,107 @@ export default class GqlAuthResolver {
return true; return true;
} }
public async passwordReset(args) { public async passwordReset(args) {
const token = args.tokenId; const token = args.tokenId;
const user = await this.users.where({reset_password_token: token}).first(); const user = await this.users
.where({ reset_password_token: token })
.first();
if (!user) { if (!user) {
throw (new Error('Invalid token')); throw new Error('Invalid token');
} }
if (user.reset_password_expires < new Date()) { if (user.reset_password_expires < new Date()) {
throw (new Error('Password reset url expired')); throw new Error('Password reset url expired');
} }
if (user.provider && user.provider !== 'local') { if (user.provider && user.provider !== 'local') {
throw (new Error('Email registered via social account')); throw new Error('Email registered via social account');
} }
const salt = await promisify(bcrypt.genSalt)(10); const salt = await promisify(bcrypt.genSalt)(10);
const password = await promisify(bcrypt.hash)(args.password, salt); const password = await promisify(bcrypt.hash)(args.password, salt);
await this.users.update({ await this.users
salt, password, .update({
reset_password_expires: null, salt,
reset_password_token: '' password,
}).where({ reset_password_expires: null,
id: user.id reset_password_token: ''
}); })
.where({
id: user.id
});
return true return true;
} }
public async passwordChange(args, { req }): Promise<any> {
public async passwordChange(args, {req}): Promise<any> { const { currentPassword, newPassword } = args;
const {currentPassword, newPassword} = args;
if (!req.isAuthenticated() || !req.user.id) { if (!req.isAuthenticated() || !req.user.id) {
throw new Error('Not authenticated') throw new Error('Not authenticated');
} }
if (!currentPassword || !newPassword) { if (!currentPassword || !newPassword) {
throw new Error('Missing new/old password') throw new Error('Missing new/old password');
} }
const user = await this.users.where({email: req.user.email}).first(); const user = await this.users.where({ email: req.user.email }).first();
const hashedPassword = await promisify(bcrypt.hash)(currentPassword, user.salt); const hashedPassword = await promisify(bcrypt.hash)(
currentPassword,
user.salt
);
if (hashedPassword !== user.password) { if (hashedPassword !== user.password) {
throw new Error('Current password is wrong') throw new Error('Current password is wrong');
} }
const salt = await promisify(bcrypt.genSalt)(10); const salt = await promisify(bcrypt.genSalt)(10);
const password = await promisify(bcrypt.hash)(newPassword, salt); const password = await promisify(bcrypt.hash)(newPassword, salt);
await this.users.update({ await this.users
salt, password .update({
}).where({id: user.id}); salt,
password
})
.where({ id: user.id });
return true; return true;
} }
public async emailVerification(args) { public async emailVerification(args) {
const token = args.tokenId; const token = args.tokenId;
const user = await this.users.where({email_verification_token: token}).first(); const user = await this.users
.where({ email_verification_token: token })
.first();
if (!user) { if (!user) {
throw new Error('Invalid verification url'); throw new Error('Invalid verification url');
} }
await this.users.update({ await this.users
email_verification_token: '', .update({
email_verified: true email_verification_token: '',
}).where({id: user.id}); email_verified: true
})
.where({ id: user.id });
return true; return true;
} }
public async me(_args, { req }) {
public async me(_args, {req}) {
return req?.user ?? {}; return req?.user ?? {};
} }
public async updateUser(req, res) { public async updateUser(req, res) {
await this.users.update({ await this.users
firstname: req.body.firstname, .update({
lastname: req.body.lastname, firstname: req.body.firstname,
}).where({ lastname: req.body.lastname
id: req.user.id })
}) .where({
res.json({msg: 'Updated successfully'}); id: req.user.id
});
res.json({ msg: 'Updated successfully' });
} }
private async createTableIfNotExist() { private async createTableIfNotExist() {
if (!(await this.dbDriver.schema.hasTable('xc_users'))) { if (!(await this.dbDriver.schema.hasTable('xc_users'))) {
await this.dbDriver.schema.createTable('xc_users', function (table) { await this.dbDriver.schema.createTable('xc_users', function(table) {
table.increments(); table.increments();
table.string('email'); table.string('email');
table.string('password', 255); table.string('password', 255);
@ -450,15 +489,15 @@ export default class GqlAuthResolver {
table.boolean('email_verified'); table.boolean('email_verified');
table.string('roles', 255).defaultTo('editor'); table.string('roles', 255).defaultTo('editor');
table.timestamps(); table.timestamps();
}) });
} }
} }
private get emailClient(): IEmailAdapter { private get emailClient(): IEmailAdapter {
return this.app?.metaMgr?.emailAdapter; return this.app?.metaMgr?.emailAdapter;
} }
}
}/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>

22
packages/nocodb/src/lib/noco/gql/GqlBaseResolver.ts

@ -1,9 +1,12 @@
export default class GqlBaseResolver { export default class GqlBaseResolver {
constructor() { constructor() {}
}
// todo: type correction // todo: type correction
public static applyMiddlewares(handlers: any[] = [], resolvers = {}, postHandlers: any[] = []): any { public static applyMiddlewares(
handlers: any[] = [],
resolvers = {},
postHandlers: any[] = []
): any {
if (!handlers) { if (!handlers) {
return resolvers; return resolvers;
} }
@ -11,31 +14,29 @@ export default class GqlBaseResolver {
resolvers[name] = async (...args) => { resolvers[name] = async (...args) => {
try { try {
for (const handler of handlers) { for (const handler of handlers) {
await handler(...args) await handler(...args);
} }
const result = await (resolver as any)(...args); const result = await (resolver as any)(...args);
if (postHandlers) { if (postHandlers) {
for (const handler of postHandlers) { for (const handler of postHandlers) {
await handler(result, ...args) await handler(result, ...args);
} }
} }
return result; return result;
} catch (e) { } catch (e) {
throw e; throw e;
} }
} };
} }
return resolvers; return resolvers;
} }
protected generateResolverFromStringBody(fnBody: string[]): any { protected generateResolverFromStringBody(fnBody: string[]): any {
if (!(fnBody && Array.isArray(fnBody) && fnBody.length)) { if (!(fnBody && Array.isArray(fnBody) && fnBody.length)) {
return; return;
} }
// @ts-ignore // @ts-ignore
let handler = (args) => { let handler = args => {
return null; return null;
}; };
@ -53,9 +54,8 @@ export default class GqlBaseResolver {
// tslint:disable-next-line:no-eval // tslint:disable-next-line:no-eval
handler = eval(js); handler = eval(js);
// console.timeEnd('startTrans') // console.timeEnd('startTrans')
} catch (e) { } catch (e) {
console.log('Error in transpilation', e) console.log('Error in transpilation', e);
} }
// tslint:disable-next-line:no-eval // tslint:disable-next-line:no-eval

27
packages/nocodb/src/lib/noco/gql/GqlCommonResolvers.ts

@ -1,16 +1,23 @@
import {BaseModelSql} from "../../dataMapper"; import { BaseModelSql } from '../../dataMapper';
export const m2mNotChildren = ({models = {}}: { models: { [key: string]: BaseModelSql } }) => { export const m2mNotChildren = ({
return async (args) => { models = {}
}: {
models: { [key: string]: BaseModelSql };
}) => {
return async args => {
return models[args?.parent]?.m2mNotChildren(args); return models[args?.parent]?.m2mNotChildren(args);
} };
} };
export const m2mNotChildrenCount = ({models = {}}: { models: { [key: string]: BaseModelSql } }) => { export const m2mNotChildrenCount = ({
return async (args) => { models = {}
}: {
models: { [key: string]: BaseModelSql };
}) => {
return async args => {
return models[args?.parent]?.m2mNotChildrenCount(args); return models[args?.parent]?.m2mNotChildrenCount(args);
} };
} };
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd

107
packages/nocodb/src/lib/noco/gql/GqlMiddleware.ts

@ -1,7 +1,7 @@
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import Handlebars from "handlebars"; import Handlebars from 'handlebars';
import {Acls} from "../../../interface/config"; import { Acls } from '../../../interface/config';
export default class GqlMiddleware { export default class GqlMiddleware {
private tn: any; private tn: any;
@ -9,7 +9,6 @@ export default class GqlMiddleware {
private models: any; private models: any;
constructor(acls: Acls, tn: string, middleWareBody?: string, models?: any) { constructor(acls: Acls, tn: string, middleWareBody?: string, models?: any) {
autoBind(this); autoBind(this);
this.acls = acls; this.acls = acls;
this.tn = tn; this.tn = tn;
@ -18,7 +17,7 @@ export default class GqlMiddleware {
if (middleWareBody) { if (middleWareBody) {
Object.defineProperty(this, 'middleware', { Object.defineProperty(this, 'middleware', {
value: this.generateResolverFromStringBody(middleWareBody) value: this.generateResolverFromStringBody(middleWareBody)
}) });
} }
} }
@ -27,12 +26,11 @@ export default class GqlMiddleware {
} }
private generateResolverFromStringBody(fnBody: string): any { private generateResolverFromStringBody(fnBody: string): any {
if (!(fnBody && fnBody.length)) { if (!(fnBody && fnBody.length)) {
return; return;
} }
// @ts-ignore // @ts-ignore
let handler = (args) => { let handler = args => {
return null; return null;
}; };
@ -41,40 +39,38 @@ export default class GqlMiddleware {
// tslint:disable-next-line:no-eval // tslint:disable-next-line:no-eval
handler = eval(js); handler = eval(js);
} catch (e) { } catch (e) {
console.log('Error in GQL Middleware transpilation', e) console.log('Error in GQL Middleware transpilation', e);
} }
return handler; return handler;
} }
// @ts-ignore // @ts-ignore
public async middleware(_args, {req, res, next}, info: any): Promise<any> { public async middleware(_args, { req, res, next }, info: any): Promise<any> {
const replaceEnvVarRec = obj => {
const replaceEnvVarRec = (obj) => {
return JSON.parse(JSON.stringify(obj), (_key, value) => { return JSON.parse(JSON.stringify(obj), (_key, value) => {
return typeof value === 'string' ? Handlebars.compile(value, {noEscape: true})({ return typeof value === 'string'
req ? Handlebars.compile(value, { noEscape: true })({
// : { req
// user: {id: 1} // (req as any).user // : {
// } // user: {id: 1} // (req as any).user
}) : value; // }
})
: value;
}); });
} };
const getOperation = (operation, fieldName) => { const getOperation = (operation, fieldName) => {
if (operation === 'mutation') { if (operation === 'mutation') {
if (fieldName.endsWith('Create')) { if (fieldName.endsWith('Create')) {
return 'create' return 'create';
} else if (fieldName.endsWith('Update')) { } else if (fieldName.endsWith('Update')) {
return 'update' return 'update';
} else if (fieldName.endsWith('Delete')) { } else if (fieldName.endsWith('Delete')) {
return 'delete' return 'delete';
} }
} }
return 'read'; return 'read';
} };
const roleOperationPossible = (roles, operation, object) => { const roleOperationPossible = (roles, operation, object) => {
res.locals.xcAcl = null; res.locals.xcAcl = null;
@ -90,7 +86,10 @@ export default class GqlMiddleware {
if (this.acl[roleName][operation]) { if (this.acl[roleName][operation]) {
return true; return true;
} }
} else if (this.acl?.[roleName]?.[operation] && roleOperationObjectGet(roleName, operation, object)) { } else if (
this.acl?.[roleName]?.[operation] &&
roleOperationObjectGet(roleName, operation, object)
) {
return true; return true;
} }
} catch (e) { } catch (e) {
@ -98,32 +97,39 @@ export default class GqlMiddleware {
} }
} }
if (errors?.length) { if (errors?.length) {
throw errors[0] throw errors[0];
} }
return false; return false;
} };
// @ts-ignore // @ts-ignore
const roleOperationObjectGet = (role, operation, object) => { const roleOperationObjectGet = (role, operation, object) => {
const columns = this.acl[role][operation].columns; const columns = this.acl[role][operation].columns;
if (columns) { if (columns) {
// todo: merge allowed columns if multiple roles // todo: merge allowed columns if multiple roles
const allowedCols = Object.keys(columns).filter(col => columns[col]) const allowedCols = Object.keys(columns).filter(col => columns[col]);
res.locals.xcAcl = {allowedCols, operation, columns}; res.locals.xcAcl = { allowedCols, operation, columns };
if (info.fieldName.endsWith('Update') || info.fieldName.endsWith('Create')) { if (
info.fieldName.endsWith('Update') ||
info.fieldName.endsWith('Create')
) {
if (Array.isArray(object)) { if (Array.isArray(object)) {
for (const row of object) { for (const row of object) {
for (const colInReq of Object.keys(row)) { for (const colInReq of Object.keys(row)) {
if (!allowedCols.includes(colInReq)) { if (!allowedCols.includes(colInReq)) {
throw new Error(`User doesn't have permission to add/edit '${colInReq}' column`); throw new Error(
`User doesn't have permission to add/edit '${colInReq}' column`
);
} }
} }
} }
} else { } else {
for (const colInReq of Object.keys(object)) { for (const colInReq of Object.keys(object)) {
if (!allowedCols.includes(colInReq)) { if (!allowedCols.includes(colInReq)) {
throw new Error(`User doesn't have permission to edit '${colInReq}' column`); throw new Error(
`User doesn't have permission to edit '${colInReq}' column`
);
} }
} }
} }
@ -131,25 +137,34 @@ export default class GqlMiddleware {
} else { } else {
if (this.acl?.[role]?.[operation]?.custom) { if (this.acl?.[role]?.[operation]?.custom) {
if (this.acl?.[role]?.[operation]?.custom) { if (this.acl?.[role]?.[operation]?.custom) {
const condition = replaceEnvVarRec(this.acl?.[role]?.[operation]?.custom) const condition = replaceEnvVarRec(
_args.conditionGraph = {condition, models: this.models}; this.acl?.[role]?.[operation]?.custom
);
_args.conditionGraph = { condition, models: this.models };
} }
} }
return Object.values(columns).some(Boolean); return Object.values(columns).some(Boolean);
} }
} }
}
const roles = (req as any)?.locals?.user?.roles ?? (req as any)?.session?.passport?.user?.roles ?? {
guest: true
}; };
const roles = (req as any)?.locals?.user?.roles ??
(req as any)?.session?.passport?.user?.roles ?? {
guest: true
};
try { try {
const allowed = roleOperationPossible(roles, getOperation(info.operation.operation, info.fieldName), _args?.data); const allowed = roleOperationPossible(
roles,
getOperation(info.operation.operation, info.fieldName),
_args?.data
);
if (allowed) { if (allowed) {
return; return;
} else { } else {
const msg = roles.guest ? `Access Denied : Please Login or Signup for a new account` : `Access Denied for this account`; const msg = roles.guest
? `Access Denied : Please Login or Signup for a new account`
: `Access Denied for this account`;
throw new Error(msg); throw new Error(msg);
} }
} catch (e) { } catch (e) {
@ -158,13 +173,13 @@ export default class GqlMiddleware {
} }
// @ts-ignore // @ts-ignore
public async postMiddleware(data, args, {req, res}, info): Promise<any> { public async postMiddleware(data, args, { req, res }, info): Promise<any> {
if (!res.locals.xcAcl) { if (!res.locals.xcAcl) {
return data; return data;
} }
// @ts-ignore // @ts-ignore
const {allowedCols, operation, columns} = res.locals.xcAcl; const { allowedCols, operation, columns } = res.locals.xcAcl;
if (!columns) { if (!columns) {
return data; return data;
@ -201,18 +216,16 @@ export default class GqlMiddleware {
delete data[colInReq]; delete data[colInReq];
} }
} }
} }
return data; return data;
} }
public async postLoaderMiddleware(...args): Promise<any> { public async postLoaderMiddleware(...args): Promise<any> {
return this.postMiddleware(args[0], args[2], args[3], args[4]) return this.postMiddleware(args[0], args[2], args[3], args[4]);
} }
}
}/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>

59
packages/nocodb/src/lib/noco/gql/GqlProcedureResolver.ts

@ -1,17 +1,20 @@
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import BaseProcedure from "../common/BaseProcedure"; import BaseProcedure from '../common/BaseProcedure';
import XcProcedure from "../common/XcProcedure"; import XcProcedure from '../common/XcProcedure';
import {GqlApiBuilder} from "./GqlApiBuilder"; import { GqlApiBuilder } from './GqlApiBuilder';
import GqlBaseResolver from "./GqlBaseResolver"; import GqlBaseResolver from './GqlBaseResolver';
export class GqlProcedureResolver
extends BaseProcedure {
export class GqlProcedureResolver extends BaseProcedure {
private acls: { [aclName: string]: { [role: string]: boolean } }; private acls: { [aclName: string]: { [role: string]: boolean } };
constructor(builder: GqlApiBuilder, functions: any[], procedures: any[], acls) { constructor(
builder: GqlApiBuilder,
functions: any[],
procedures: any[],
acls
) {
super(); super();
autoBind(this); autoBind(this);
this.builder = builder; this.builder = builder;
@ -22,90 +25,90 @@ export class GqlProcedureResolver
} }
public fnHandler(name) { public fnHandler(name) {
return (async (args) => { return (async args => {
let body = []; let body = [];
try { try {
body = JSON.parse(args.body); body = JSON.parse(args.body);
} catch (_e) { } catch (_e) {}
}
const result = await this.xcProcedure.callFunction(name, body); const result = await this.xcProcedure.callFunction(name, body);
return JSON.stringify(result, null, 2); return JSON.stringify(result, null, 2);
}).bind(this) }).bind(this);
} }
private procHandler(name) { private procHandler(name) {
// @ts-ignore // @ts-ignore
return (async (args) => { return (async args => {
let body = []; let body = [];
try { try {
body = JSON.parse(args.body); body = JSON.parse(args.body);
} catch (_e) { } catch (_e) {}
}
const result = await this.xcProcedure.callProcedure(name, body); const result = await this.xcProcedure.callProcedure(name, body);
return JSON.stringify(result, null, 2); return JSON.stringify(result, null, 2);
}).bind(this) }).bind(this);
} }
public mapResolvers(): any { public mapResolvers(): any {
const resolvers = {}; const resolvers = {};
if (this.functions) { if (this.functions) {
for (const {function_name} of this.functions) { for (const { function_name } of this.functions) {
resolvers[`_${function_name}`] = this.fnHandler(function_name); resolvers[`_${function_name}`] = this.fnHandler(function_name);
} }
} }
if (this.procedures) { if (this.procedures) {
for (const {procedure_name} of this.procedures) { for (const { procedure_name } of this.procedures) {
resolvers[`_${procedure_name}`] = this.procHandler(procedure_name); resolvers[`_${procedure_name}`] = this.procHandler(procedure_name);
} }
} }
return GqlBaseResolver.applyMiddlewares([this.middleware], resolvers); return GqlBaseResolver.applyMiddlewares([this.middleware], resolvers);
} }
public getSchema() { public getSchema() {
if (!this.functions?.length && !this.procedures?.length) { if (!this.functions?.length && !this.procedures?.length) {
return '' return '';
} }
let resolvers = ` let resolvers = `
type Mutation { type Mutation {
`; `;
if (this.functions) { if (this.functions) {
for (const {function_name} of this.functions) { for (const { function_name } of this.functions) {
resolvers += `_${function_name}(body:String):String\r\n`; resolvers += `_${function_name}(body:String):String\r\n`;
} }
} }
if (this.procedures) { if (this.procedures) {
for (const {procedure_name} of this.procedures) { for (const { procedure_name } of this.procedures) {
resolvers += `_${procedure_name}(body:String):String\r\n`; resolvers += `_${procedure_name}(body:String):String\r\n`;
} }
} }
return resolvers + `}\r\n`; return resolvers + `}\r\n`;
} }
updateMiddlewareBody(_body: string) { } updateMiddlewareBody(_body: string) {}
private async middleware(_args, {req}, info: any) { private async middleware(_args, { req }, info: any) {
const roles = (req as any)?.session?.passport?.user?.roles ?? { const roles = (req as any)?.session?.passport?.user?.roles ?? {
guest: true guest: true
}; };
try { try {
const allowed = Object.keys(roles).some(role => roles[role] && this.acls?.[info.fieldName.slice(1)]?.[role]); const allowed = Object.keys(roles).some(
role => roles[role] && this.acls?.[info.fieldName.slice(1)]?.[role]
);
if (allowed) { if (allowed) {
// any additional rules can be made here // any additional rules can be made here
return; return;
} else { } else {
const msg = roles.guest ? `Access Denied : Please Login or Signup for a new account` : `Access Denied for this account`; const msg = roles.guest
? `Access Denied : Please Login or Signup for a new account`
: `Access Denied for this account`;
throw new Error(msg); throw new Error(msg);
} }
} catch (e) { } catch (e) {
throw e throw e;
} }
} }
} }
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *

215
packages/nocodb/src/lib/noco/gql/GqlResolver.ts

@ -1,30 +1,36 @@
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import {Acls} from "../../../interface/config"; import { Acls } from '../../../interface/config';
import {BaseModelSql} from "../../dataMapper"; import { BaseModelSql } from '../../dataMapper';
import Noco from "../Noco"; import Noco from '../Noco';
import GqlBaseResolver from "./GqlBaseResolver"; import GqlBaseResolver from './GqlBaseResolver';
import GqlMiddleware from "./GqlMiddleware"; import GqlMiddleware from './GqlMiddleware';
function parseHrtimeToSeconds(hrtime) { function parseHrtimeToSeconds(hrtime) {
const seconds = (hrtime[0] + (hrtime[1] / 1e6)).toFixed(3); const seconds = (hrtime[0] + hrtime[1] / 1e6).toFixed(3);
return seconds; return seconds;
} }
export default class GqlResolver extends GqlBaseResolver { export default class GqlResolver extends GqlBaseResolver {
// @ts-ignore // @ts-ignore
private app: Noco; private app: Noco;
private models: { [key: string]: BaseModelSql }; private models: { [key: string]: BaseModelSql };
private table: string; private table: string;
private typeClass: new(obj: any) => any; private typeClass: new (obj: any) => any;
private acls: Acls; private acls: Acls;
private functions: { [key: string]: any }; private functions: { [key: string]: any };
private middlewareStringBody?: string; private middlewareStringBody?: string;
constructor(
constructor(app: Noco, models: { [key: string]: BaseModelSql }, table: string, typeClass: { new(obj: any): any }, acls: Acls, functions: { [key: string]: string[] }, middlewareStringBody?: string) { app: Noco,
models: { [key: string]: BaseModelSql },
table: string,
typeClass: { new (obj: any): any },
acls: Acls,
functions: { [key: string]: string[] },
middlewareStringBody?: string
) {
super(); super();
autoBind(this); autoBind(this);
this.app = app; this.app = app;
@ -36,19 +42,24 @@ export default class GqlResolver extends GqlBaseResolver {
this.middlewareStringBody = middlewareStringBody; this.middlewareStringBody = middlewareStringBody;
} }
private get model(): BaseModelSql { private get model(): BaseModelSql {
return this.models?.[this.table]; return this.models?.[this.table];
} }
public async list(args, {req, res}: { req: any & { model: BaseModelSql }, res: any }): Promise<any> { public async list(
args,
{ req, res }: { req: any & { model: BaseModelSql }; res: any }
): Promise<any> {
const startTime = process.hrtime(); const startTime = process.hrtime();
try { try {
if (args.conditionGraph && typeof args.conditionGraph === 'string') { if (args.conditionGraph && typeof args.conditionGraph === 'string') {
args.conditionGraph = {models: this.models, condition: JSON.parse(args.conditionGraph)} args.conditionGraph = {
models: this.models,
condition: JSON.parse(args.conditionGraph)
};
} }
if (args.condition && typeof args.condition === 'string') { if (args.condition && typeof args.condition === 'string') {
args.condition = JSON.parse(args.condition) args.condition = JSON.parse(args.condition);
} }
} catch (e) { } catch (e) {
/* ignore parse error */ /* ignore parse error */
@ -57,63 +68,66 @@ export default class GqlResolver extends GqlBaseResolver {
const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime)); const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime));
res.setHeader('xc-db-response', elapsedSeconds); res.setHeader('xc-db-response', elapsedSeconds);
return (data).map(o => { return data.map(o => {
return new req.gqlType(o); return new req.gqlType(o);
}); });
} }
public async create(args, {req}): Promise<any> { public async create(args, { req }): Promise<any> {
const data = await req.model.insert(args.data, null, req); const data = await req.model.insert(args.data, null, req);
return new req.gqlType(data); return new req.gqlType(data);
} }
public async read(args, {req}): Promise<any> { public async read(args, { req }): Promise<any> {
const data = await req.model.readByPk(args.id, args); const data = await req.model.readByPk(args.id, args);
return new req.gqlType(data); return new req.gqlType(data);
} }
public async update(args, {req}): Promise<any> { public async update(args, { req }): Promise<any> {
const data = await req.model.updateByPk(args.id, args.data, null, req); const data = await req.model.updateByPk(args.id, args.data, null, req);
return data; return data;
} }
public async delete(args, {req}): Promise<any> { public async delete(args, { req }): Promise<any> {
const data = await req.model.delByPk(args.id, null, req); const data = await req.model.delByPk(args.id, null, req);
return data; return data;
} }
public async exists(args, {req}): Promise<any> { public async exists(args, { req }): Promise<any> {
const data = await req.model.exists(args.id, args); const data = await req.model.exists(args.id, args);
return data; return data;
} }
public async findOne(args, {req}): Promise<any> { public async findOne(args, { req }): Promise<any> {
const data = await req.model.findOne(args); const data = await req.model.findOne(args);
return new req.gqlType(data); return new req.gqlType(data);
} }
public async groupBy(args, {req}): Promise<any> { public async groupBy(args, { req }): Promise<any> {
const data = await req.model.groupBy(args); const data = await req.model.groupBy(args);
return data; return data;
} }
public async aggregate(args, {req}): Promise<any> { public async aggregate(args, { req }): Promise<any> {
const data = await req.model.aggregate(args); const data = await req.model.aggregate(args);
return data; return data;
} }
public async distinct(args, {req}): Promise<any> { public async distinct(args, { req }): Promise<any> {
const data = (await req.model.distinct(args)).map(d => new req.gqlType(d)); const data = (await req.model.distinct(args)).map(d => new req.gqlType(d));
return data; return data;
} }
public async count(args, {req}): Promise<any> { public async count(args, { req }): Promise<any> {
try { try {
if (args.conditionGraph && typeof args.conditionGraph === 'string') { if (args.conditionGraph && typeof args.conditionGraph === 'string') {
args.conditionGraph = {models: this.models, condition: JSON.parse(args.conditionGraph)} args.conditionGraph = {
models: this.models,
condition: JSON.parse(args.conditionGraph)
};
} }
if (args.condition && typeof args.condition === 'string') { if (args.condition && typeof args.condition === 'string') {
args.condition = JSON.parse(args.condition) args.condition = JSON.parse(args.condition);
} }
} catch (e) { } catch (e) {
/* ignore parse error */ /* ignore parse error */
@ -122,62 +136,145 @@ export default class GqlResolver extends GqlBaseResolver {
return data.count; return data.count;
} }
public async distribution(args, {req}): Promise<any> { public async distribution(args, { req }): Promise<any> {
const data = await req.model.distribution(args); const data = await req.model.distribution(args);
return data; return data;
} }
public async createb(args, {req}): Promise<any> { public async createb(args, { req }): Promise<any> {
const data = await req.model.insertb(args.data); const data = await req.model.insertb(args.data);
return data; return data;
} }
public async updateb(args, {req}): Promise<any> { public async updateb(args, { req }): Promise<any> {
const data = await req.model.updateb(args.data); const data = await req.model.updateb(args.data);
return data; return data;
} }
public async deleteb(args, {req}): Promise<any> { public async deleteb(args, { req }): Promise<any> {
const data = await req.model.delb(args.data); const data = await req.model.delb(args.data);
return data; return data;
} }
public updateMiddlewareBody(body: string): this { public updateMiddlewareBody(body: string): this {
this.middlewareStringBody = body; this.middlewareStringBody = body;
return this; return this;
} }
public mapResolvers(customResolver: any): any { public mapResolvers(customResolver: any): any {
const mw = new GqlMiddleware(this.acls, this.table, this.middlewareStringBody, this.models); const mw = new GqlMiddleware(
this.acls,
this.table,
this.middlewareStringBody,
this.models
);
// todo: replace with inflection // todo: replace with inflection
const name = this.model._tn; const name = this.model._tn;
return GqlResolver.applyMiddlewares([(_, {req}) => { return GqlResolver.applyMiddlewares(
req.models = this.models; [
req.model = this.model; (_, { req }) => {
req.gqlType = this.typeClass; req.models = this.models;
}, mw.middleware], { req.model = this.model;
req.gqlType = this.typeClass;
...(customResolver?.additional?.[this.table] || {}), },
mw.middleware
[`${name}List`]: customResolver?.override?.[`${name}List`] || this.generateResolverFromStringBody(this.functions[`${name}List`]) || this.list, ],
[`${name}FindOne`]: customResolver?.override?.[`${name}FindOne`] || this.generateResolverFromStringBody(this.functions[`${name}FindOne`]) || this.findOne, {
[`${name}Count`]: customResolver?.override?.[`${name}Count`] || this.generateResolverFromStringBody(this.functions[`${name}Count`]) || this.count, ...(customResolver?.additional?.[this.table] || {}),
[`${name}Distinct`]: customResolver?.override?.[`${name}Distinct`] || this.generateResolverFromStringBody(this.functions[`${name}Distinct`]) || this.distinct,
[`${name}GroupBy`]: customResolver?.override?.[`${name}GroupBy`] || this.generateResolverFromStringBody(this.functions[`${name}GroupBy`]) || this.groupBy, [`${name}List`]:
[`${name}Aggregate`]: customResolver?.override?.[`${name}Aggregate`] || this.generateResolverFromStringBody(this.functions[`${name}Aggregate`]) || this.aggregate, customResolver?.override?.[`${name}List`] ||
[`${name}Distribution`]: customResolver?.override?.[`${name}Distribution`] || this.generateResolverFromStringBody(this.functions[`${name}Distribution`]) || this.distribution, this.generateResolverFromStringBody(this.functions[`${name}List`]) ||
...(this.model.type === 'table' ? { this.list,
[`${name}Read`]: customResolver?.override?.[`${name}Read`] || this.generateResolverFromStringBody(this.functions[`${name}Read`]) || this.read, [`${name}FindOne`]:
[`${name}Exists`]: customResolver?.override?.[`${name}Exists`] || this.generateResolverFromStringBody(this.functions[`${name}Exists`]) || this.exists, customResolver?.override?.[`${name}FindOne`] ||
[`${name}Create`]: customResolver?.override?.[`${name}Create`] || this.generateResolverFromStringBody(this.functions[`${name}Create`]) || this.create, this.generateResolverFromStringBody(
[`${name}Update`]: customResolver?.override?.[`${name}Update`] || this.generateResolverFromStringBody(this.functions[`${name}Update`]) || this.update, this.functions[`${name}FindOne`]
[`${name}Delete`]: customResolver?.override?.[`${name}Delete`] || this.generateResolverFromStringBody(this.functions[`${name}Delete`]) || this.delete, ) ||
[`${name}CreateBulk`]: customResolver?.override?.[`${name}CreateBulk`] || this.generateResolverFromStringBody(this.functions[`${name}CreateBulk`]) || this.createb, this.findOne,
[`${name}UpdateBulk`]: customResolver?.override?.[`${name}UpdateBulk`] || this.generateResolverFromStringBody(this.functions[`${name}UpdateBulk`]) || this.updateb, [`${name}Count`]:
[`${name}DeleteBulk`]: customResolver?.override?.[`${name}DeleteBulk`] || this.generateResolverFromStringBody(this.functions[`${name}DeleteBulk`]) || this.deleteb, customResolver?.override?.[`${name}Count`] ||
} : {}) this.generateResolverFromStringBody(this.functions[`${name}Count`]) ||
}, [mw.postMiddleware]); this.count,
[`${name}Distinct`]:
customResolver?.override?.[`${name}Distinct`] ||
this.generateResolverFromStringBody(
this.functions[`${name}Distinct`]
) ||
this.distinct,
[`${name}GroupBy`]:
customResolver?.override?.[`${name}GroupBy`] ||
this.generateResolverFromStringBody(
this.functions[`${name}GroupBy`]
) ||
this.groupBy,
[`${name}Aggregate`]:
customResolver?.override?.[`${name}Aggregate`] ||
this.generateResolverFromStringBody(
this.functions[`${name}Aggregate`]
) ||
this.aggregate,
[`${name}Distribution`]:
customResolver?.override?.[`${name}Distribution`] ||
this.generateResolverFromStringBody(
this.functions[`${name}Distribution`]
) ||
this.distribution,
...(this.model.type === 'table'
? {
[`${name}Read`]:
customResolver?.override?.[`${name}Read`] ||
this.generateResolverFromStringBody(
this.functions[`${name}Read`]
) ||
this.read,
[`${name}Exists`]:
customResolver?.override?.[`${name}Exists`] ||
this.generateResolverFromStringBody(
this.functions[`${name}Exists`]
) ||
this.exists,
[`${name}Create`]:
customResolver?.override?.[`${name}Create`] ||
this.generateResolverFromStringBody(
this.functions[`${name}Create`]
) ||
this.create,
[`${name}Update`]:
customResolver?.override?.[`${name}Update`] ||
this.generateResolverFromStringBody(
this.functions[`${name}Update`]
) ||
this.update,
[`${name}Delete`]:
customResolver?.override?.[`${name}Delete`] ||
this.generateResolverFromStringBody(
this.functions[`${name}Delete`]
) ||
this.delete,
[`${name}CreateBulk`]:
customResolver?.override?.[`${name}CreateBulk`] ||
this.generateResolverFromStringBody(
this.functions[`${name}CreateBulk`]
) ||
this.createb,
[`${name}UpdateBulk`]:
customResolver?.override?.[`${name}UpdateBulk`] ||
this.generateResolverFromStringBody(
this.functions[`${name}UpdateBulk`]
) ||
this.updateb,
[`${name}DeleteBulk`]:
customResolver?.override?.[`${name}DeleteBulk`] ||
this.generateResolverFromStringBody(
this.functions[`${name}DeleteBulk`]
) ||
this.deleteb
}
: {})
},
[mw.postMiddleware]
);
} }
} }
/** /**

3
packages/nocodb/src/lib/noco/gql/auth/schema.ts

@ -41,7 +41,8 @@ type User {
type XcToken{ type XcToken{
token: String token: String
} }
`/** `;
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>

3
packages/nocodb/src/lib/noco/gql/common.schema.ts

@ -49,7 +49,8 @@ type Query{
`/** `;
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>

2
packages/nocodb/src/lib/noco/gql/emailTemplate/forgotPassword.ts

@ -169,7 +169,7 @@ table[class=body] .article {
</table> </table>
</body> </body>
</html> </html>
` `;
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *

2
packages/nocodb/src/lib/noco/gql/emailTemplate/verify.ts

@ -206,7 +206,7 @@ export default `<!doctype html>
</table> </table>
</body> </body>
</html> </html>
` `;
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd

30
packages/nocodb/src/lib/noco/meta/MetaAPILogger.ts

@ -1,8 +1,7 @@
import {XKnex} from "../../dataMapper"; import { XKnex } from '../../dataMapper';
import {Request} from 'express'; import { Request } from 'express';
export default class MetaAPILogger { export default class MetaAPILogger {
static _instance: MetaAPILogger; static _instance: MetaAPILogger;
knex: XKnex; knex: XKnex;
@ -13,7 +12,7 @@ export default class MetaAPILogger {
filename: 'noco_log.db' filename: 'noco_log.db'
}, },
useNullAsDefault: true useNullAsDefault: true
}) });
} }
async init() { async init() {
@ -23,29 +22,26 @@ export default class MetaAPILogger {
}); });
} }
static async mw(req, res, next) { static async mw(req, res, next) {
if (process.env.NC_LOGGER) { if (process.env.NC_LOGGER) {
const oldWrite = res.write, const oldWrite = res.write,
oldEnd = res.end; oldEnd = res.end;
const chunks = []; const chunks = [];
res.write = function (chunk) { res.write = function(chunk) {
chunks.push(chunk); chunks.push(chunk);
// eslint-disable-next-line prefer-rest-params // eslint-disable-next-line prefer-rest-params
oldWrite.apply(res, arguments); oldWrite.apply(res, arguments);
}; };
res.end = function (chunk) { res.end = function(chunk) {
if (chunk) if (chunk) chunks.push(chunk);
chunks.push(chunk);
const body = Buffer.concat(chunks).toString('utf8'); const body = Buffer.concat(chunks).toString('utf8');
MetaAPILogger.log(req, body) MetaAPILogger.log(req, body);
// eslint-disable-next-line prefer-rest-params // eslint-disable-next-line prefer-rest-params
oldEnd.apply(res, arguments); oldEnd.apply(res, arguments);
}; };
@ -59,7 +55,7 @@ export default class MetaAPILogger {
} }
if (!this._instance) { if (!this._instance) {
this._instance = new MetaAPILogger(); this._instance = new MetaAPILogger();
await this._instance.init() await this._instance.init();
} }
await this._instance.knex('nc_log').insert({ await this._instance.knex('nc_log').insert({
path: req.url, path: req.url,
@ -69,10 +65,8 @@ export default class MetaAPILogger {
method: req.method, method: req.method,
operation: req.body?.api, operation: req.body?.api,
response: typeof res === 'string' ? res : JSON.stringify(res) response: typeof res === 'string' ? res : JSON.stringify(res)
}) });
} }
} }
class XcLoggerMigrationSource { class XcLoggerMigrationSource {
@ -81,7 +75,7 @@ class XcLoggerMigrationSource {
// arguments to getMigrationName and getMigration // arguments to getMigrationName and getMigration
public getMigrations(): Promise<any> { public getMigrations(): Promise<any> {
// In this example we are just returning migration names // In this example we are just returning migration names
return Promise.resolve(['logger']) return Promise.resolve(['logger']);
} }
public getMigrationName(migration): string { public getMigrationName(migration): string {
@ -104,10 +98,10 @@ class XcLoggerMigrationSource {
table.text('response'); table.text('response');
table.text('comments'); table.text('comments');
table.timestamps(true, true); table.timestamps(true, true);
}) });
}, },
async down(knex) { async down(knex) {
await knex.schema.dropTable('nc_log') await knex.schema.dropTable('nc_log');
} }
}; };
} }

285
packages/nocodb/src/lib/noco/meta/NcMetaIO.ts

@ -1,26 +1,60 @@
import {NcConfig} from "../../../interface/config"; import { NcConfig } from '../../../interface/config';
import Noco from "../Noco"; import Noco from '../Noco';
import {XKnex} from "../../dataMapper"; import { XKnex } from '../../dataMapper';
const META_TABLES = { const META_TABLES = {
graphql: ['nc_models', 'nc_resolvers', 'nc_loaders', 'nc_store', 'nc_hooks', 'nc_roles', 'nc_acl', 'nc_api_tokens', 'nc_relations', 'nc_migrations', graphql: [
'nc_models',
'nc_resolvers',
'nc_loaders',
'nc_store',
'nc_hooks',
'nc_roles',
'nc_acl',
'nc_api_tokens',
'nc_relations',
'nc_migrations',
'nc_disabled_models_for_role', 'nc_disabled_models_for_role',
'nc_shared_views', 'nc_cron', 'nc_audit' 'nc_shared_views',
'nc_cron',
'nc_audit'
],
grpc: [
'nc_models',
'nc_rpc',
'nc_store',
'nc_hooks',
'nc_roles',
'nc_acl',
'nc_relations',
'nc_migrations',
'nc_api_tokens',
'nc_disabled_models_for_role',
'nc_shared_views',
'nc_cron'
], ],
grpc: ['nc_models', 'nc_rpc', 'nc_store', 'nc_hooks', 'nc_roles', 'nc_acl', 'nc_relations', 'nc_migrations', 'nc_api_tokens', 'nc_disabled_models_for_role',
'nc_shared_views', 'nc_cron'],
rest: [ rest: [
'nc_models', 'nc_routes', 'nc_store', 'nc_hooks', 'nc_roles', 'nc_acl', 'nc_relations', 'nc_migrations', 'nc_api_tokens', 'nc_models',
'nc_routes',
'nc_store',
'nc_hooks',
'nc_roles',
'nc_acl',
'nc_relations',
'nc_migrations',
'nc_api_tokens',
'nc_disabled_models_for_role', 'nc_disabled_models_for_role',
'nc_shared_views', 'nc_cron', 'nc_audit'], 'nc_shared_views',
} 'nc_cron',
'nc_audit'
]
};
export default abstract class NcMetaIO { export default abstract class NcMetaIO {
protected app: Noco; protected app: Noco;
protected config: NcConfig; protected config: NcConfig;
public abstract get knexConnection(): XKnex ; public abstract get knexConnection(): XKnex;
constructor(app: Noco, config: NcConfig) { constructor(app: Noco, config: NcConfig) {
this.app = app; this.app = app;
@ -29,104 +63,135 @@ export default abstract class NcMetaIO {
public abstract metaInit(): Promise<boolean>; public abstract metaInit(): Promise<boolean>;
public abstract metaInsert(project_id: string, public abstract metaInsert(
dbAlias: string, project_id: string,
target: string, dbAlias: string,
data: any) target: string,
: Promise<any>; data: any
): Promise<any>;
public abstract audit(project_id: string,
dbAlias: string, public abstract audit(
target: string, project_id: string,
data: any) dbAlias: string,
: Promise<any>; target: string,
data: any
public abstract metaUpdate(project_id: string, ): Promise<any>;
dbAlias: string,
target: string, public abstract metaUpdate(
data: any, project_id: string,
idOrCondition: string | { [key: string]: any }, dbAlias: string,
xcCondition?: XcCondition) target: string,
: Promise<void>; data: any,
idOrCondition: string | { [key: string]: any },
public abstract metaDelete(project_id: string, xcCondition?: XcCondition
dbAlias: string, ): Promise<void>;
target: string,
idOrCondition?: string | { [key: string]: any }, public abstract metaDelete(
xcCondition?: XcCondition) project_id: string,
: Promise<void>; dbAlias: string,
target: string,
public abstract metaDeleteAll(project_id: string, idOrCondition?: string | { [key: string]: any },
dbAlias: string) xcCondition?: XcCondition
: Promise<void>; ): Promise<void>;
public abstract metaGet(project_id: string, public abstract metaDeleteAll(
dbAlias: string, project_id: string,
target: string, dbAlias: string
idOrCondition: string | { [key: string]: any }, ): Promise<void>;
fields?: string[], xcCondition?: XcCondition)
: Promise<any>; public abstract metaGet(
project_id: string,
public abstract metaList(project_id: string, dbAlias: string,
dbAlias: string, target: string,
target: string, idOrCondition: string | { [key: string]: any },
args?: { fields?: string[],
condition?: { [key: string]: any }, xcCondition?: XcCondition
limit?: number, ): Promise<any>;
offset?: number,
xcCondition?: XcCondition, public abstract metaList(
fields?: string[] project_id: string,
}): Promise<any[]>; dbAlias: string,
target: string,
public abstract metaPaginatedList(project_id: string, args?: {
dbAlias: string, condition?: { [key: string]: any };
target: string, limit?: number;
args?: { offset?: number;
condition?: { [key: string]: any }, xcCondition?: XcCondition;
limit?: number, fields?: string[];
offset?: number, }
xcCondition?: XcCondition, ): Promise<any[]>;
fields?: string[],
sort?: { field: string, desc?: boolean } public abstract metaPaginatedList(
}): Promise<{ project_id: string,
list: any[], dbAlias: string,
count: number target: string,
args?: {
condition?: { [key: string]: any };
limit?: number;
offset?: number;
xcCondition?: XcCondition;
fields?: string[];
sort?: { field: string; desc?: boolean };
}
): Promise<{
list: any[];
count: number;
}>; }>;
public abstract isMetaDataExists(
public abstract isMetaDataExists(project_id: string, project_id: string,
dbAlias: string): Promise<boolean>; dbAlias: string
): Promise<boolean>;
public abstract metaReset(project_id: string,
dbAlias: string, apiType?: string): Promise<void>; public abstract metaReset(
project_id: string,
public abstract projectCreate(projectName: string, dbAlias: string,
config: any, apiType?: string
description?: string, ): Promise<void>;
meta?:boolean): Promise<any>;
public abstract projectCreate(
public abstract projectUpdate(projectId: string, projectName: string,
config: any): Promise<any>; config: any,
description?: string,
public abstract projectAddUser(projectId: string, meta?: boolean
userId: any, roles: string): Promise<any>; ): Promise<any>;
public abstract projectRemoveUser(projectId: string, public abstract projectUpdate(projectId: string, config: any): Promise<any>;
userId: any): Promise<any>;
public abstract projectAddUser(
public abstract projectStatusUpdate(projectName: string, projectId: string,
status: string): Promise<any>; userId: any,
roles: string
): Promise<any>;
public abstract projectRemoveUser(
projectId: string,
userId: any
): Promise<any>;
public abstract projectStatusUpdate(
projectName: string,
status: string
): Promise<any>;
public abstract projectList(): Promise<any[]>; public abstract projectList(): Promise<any[]>;
public abstract userProjectList(userId: any): Promise<any[]>; public abstract userProjectList(userId: any): Promise<any[]>;
public abstract isUserHaveAccessToProject(projectId: string, public abstract isUserHaveAccessToProject(
userId: any): Promise<boolean>; projectId: string,
userId: any
): Promise<boolean>;
public abstract projectGet(projectName: string, encrypt?: boolean): Promise<any>; public abstract projectGet(
projectName: string,
encrypt?: boolean
): Promise<any>;
public abstract projectGetById(projectId: string, encrypt?: boolean): Promise<any>; public abstract projectGetById(
projectId: string,
encrypt?: boolean
): Promise<any>;
public abstract projectDelete(title: string): Promise<any>; public abstract projectDelete(title: string): Promise<any>;
public abstract projectDeleteById(id: string): Promise<any>; public abstract projectDeleteById(id: string): Promise<any>;
@ -142,11 +207,19 @@ export default abstract class NcMetaIO {
public setConfig(config: NcConfig) { public setConfig(config: NcConfig) {
this.config = config; this.config = config;
} }
} }
type XcConditionStr = { type XcConditionStr = {
[key in 'lt' | 'gt' | 'le' | 'ge' | 'like' | 'nlike' | 'eq' | 'in' | 'nin']: any; [key in
| 'lt'
| 'gt'
| 'le'
| 'ge'
| 'like'
| 'nlike'
| 'eq'
| 'in'
| 'nin']: any;
}; };
interface XcCondition { interface XcCondition {
@ -155,12 +228,10 @@ interface XcCondition {
_not?: XcCondition; _not?: XcCondition;
[key: string]: XcConditionStr | any; [key: string]: XcConditionStr | any;
} }
export { export { META_TABLES };
META_TABLES /**
}/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>

444
packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts

@ -1,35 +1,42 @@
import CryptoJS from 'crypto-js'; import CryptoJS from 'crypto-js';
import {customAlphabet} from 'nanoid' import { customAlphabet } from 'nanoid';
import {NcConfig} from "../../../interface/config"; import { NcConfig } from '../../../interface/config';
import {Knex, XKnex} from "../../dataMapper"; import { Knex, XKnex } from '../../dataMapper';
import Noco from "../Noco"; import Noco from '../Noco';
import XcMigrationSource from "../common/XcMigrationSource"; import XcMigrationSource from '../common/XcMigrationSource';
import NcMetaIO, {META_TABLES} from "./NcMetaIO"; import NcMetaIO, { META_TABLES } from './NcMetaIO';
import NcConnectionMgr from "../common/NcConnectionMgr"; import NcConnectionMgr from '../common/NcConnectionMgr';
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4)
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4);
export default class NcMetaIOImpl extends NcMetaIO { export default class NcMetaIOImpl extends NcMetaIO {
public async metaPaginatedList(
projectId: string,
public async metaPaginatedList(projectId: string, dbAlias: string, target: string, args?: { condition?: { [key: string]: any; }; limit?: number; offset?: number; xcCondition?; fields?: string[]; sort?: { field: string, desc?: boolean } }): Promise<{ list: any[]; count: number; }> { dbAlias: string,
const query = this.knexConnection(target) target: string,
const countQuery = this.knexConnection(target) args?: {
condition?: { [key: string]: any };
limit?: number;
offset?: number;
xcCondition?;
fields?: string[];
sort?: { field: string; desc?: boolean };
}
): Promise<{ list: any[]; count: number }> {
const query = this.knexConnection(target);
const countQuery = this.knexConnection(target);
if (projectId !== null) { if (projectId !== null) {
query.where('project_id', projectId) query.where('project_id', projectId);
countQuery.where('project_id', projectId) countQuery.where('project_id', projectId);
} }
if (dbAlias !== null) { if (dbAlias !== null) {
query.where('db_alias', dbAlias); query.where('db_alias', dbAlias);
countQuery.where('db_alias', dbAlias); countQuery.where('db_alias', dbAlias);
} }
if (args?.condition) { if (args?.condition) {
query.where(args.condition); query.where(args.condition);
countQuery.where(args.condition); countQuery.where(args.condition);
@ -44,27 +51,25 @@ export default class NcMetaIOImpl extends NcMetaIO {
query.offset(args.offset); query.offset(args.offset);
} }
if (args?.xcCondition) { if (args?.xcCondition) {
(query as any).condition(args.xcCondition) (query as any)
(countQuery as any).condition(args.xcCondition) .condition(args.xcCondition)(countQuery as any)
.condition(args.xcCondition);
} }
if (args?.fields?.length) { if (args?.fields?.length) {
query.select(...args.fields) query.select(...args.fields);
} }
return { return {
list: await query, list: await query,
count: (Object.values(await countQuery.count().first()))?.[0] as any count: Object.values(await countQuery.count().first())?.[0] as any
}; };
} }
private connection: XKnex; private connection: XKnex;
// todo: need to fix // todo: need to fix
private trx: Knex.Transaction; private trx: Knex.Transaction;
constructor(app: Noco, config: NcConfig) { constructor(app: Noco, config: NcConfig) {
super(app, config); super(app, config);
@ -75,9 +80,13 @@ export default class NcMetaIOImpl extends NcMetaIO {
if (this.config?.meta?.db) { if (this.config?.meta?.db) {
this.connection = XKnex(this.config?.meta?.db); this.connection = XKnex(this.config?.meta?.db);
} else { } else {
let dbIndex = this.config.envs?.[this.config.workingEnv]?.db.findIndex(c => c.meta.dbAlias === this.config?.auth?.jwt?.dbAlias) let dbIndex = this.config.envs?.[this.config.workingEnv]?.db.findIndex(
c => c.meta.dbAlias === this.config?.auth?.jwt?.dbAlias
);
dbIndex = dbIndex === -1 ? 0 : dbIndex; dbIndex = dbIndex === -1 ? 0 : dbIndex;
this.connection = XKnex(this.config.envs?.[this.config.workingEnv]?.db[dbIndex] as any); this.connection = XKnex(
this.config.envs?.[this.config.workingEnv]?.db[dbIndex] as any
);
} }
NcConnectionMgr.setXcMeta(this); NcConnectionMgr.setXcMeta(this);
} }
@ -107,9 +116,8 @@ export default class NcMetaIOImpl extends NcMetaIO {
): Promise<void> { ): Promise<void> {
const query = this.knexConnection(target); const query = this.knexConnection(target);
if (project_id !== null) { if (project_id !== null) {
query.where('project_id', project_id) query.where('project_id', project_id);
} }
if (dbAlias !== null) { if (dbAlias !== null) {
query.where('db_alias', dbAlias); query.where('db_alias', dbAlias);
@ -122,31 +130,32 @@ export default class NcMetaIOImpl extends NcMetaIO {
} }
if (xcCondition) { if (xcCondition) {
query.condition(xcCondition, {}) query.condition(xcCondition, {});
} }
return query.del(); return query.del();
} }
public async metaGet(project_id: string, public async metaGet(
dbAlias: string, project_id: string,
target: string, dbAlias: string,
idOrCondition: string | { [p: string]: any }, target: string,
fields?: string[], xcCondition?): Promise<any> { idOrCondition: string | { [p: string]: any },
fields?: string[],
xcCondition?
): Promise<any> {
const query = this.knexConnection(target); const query = this.knexConnection(target);
if (xcCondition) { if (xcCondition) {
query.condition(xcCondition) query.condition(xcCondition);
} }
if (fields?.length) { if (fields?.length) {
query.select(...fields) query.select(...fields);
} }
if (project_id !== null) { if (project_id !== null) {
query.where('project_id', project_id) query.where('project_id', project_id);
} }
if (dbAlias !== null) { if (dbAlias !== null) {
query.where('db_alias', dbAlias); query.where('db_alias', dbAlias);
@ -161,36 +170,47 @@ export default class NcMetaIOImpl extends NcMetaIO {
query.where(idOrCondition); query.where(idOrCondition);
} }
// console.log(query.toQuery()) // console.log(query.toQuery())
return query.first(); return query.first();
} }
public async metaInsert(
public async metaInsert(project_id: string, dbAlias: string, target: string, data: any): Promise<any> { project_id: string,
dbAlias: string,
target: string,
data: any
): Promise<any> {
return this.knexConnection(target).insert({ return this.knexConnection(target).insert({
'db_alias': dbAlias, db_alias: dbAlias,
project_id, project_id,
created_at: this.knexConnection?.fn?.now(), created_at: this.knexConnection?.fn?.now(),
updated_at: this.knexConnection?.fn?.now(), ...data updated_at: this.knexConnection?.fn?.now(),
...data
}); });
} }
public async metaList(project_id: string, dbAlias: string, target: string, args?: { public async metaList(
condition?: { [p: string]: any }; limit?: number; offset?: number, xcCondition?, project_id: string,
fields?: string[] dbAlias: string,
}): Promise<any[]> { target: string,
const query = this.knexConnection(target) args?: {
condition?: { [p: string]: any };
limit?: number;
offset?: number;
xcCondition?;
fields?: string[];
}
): Promise<any[]> {
const query = this.knexConnection(target);
if (project_id !== null) { if (project_id !== null) {
query.where('project_id', project_id) query.where('project_id', project_id);
} }
if (dbAlias !== null) { if (dbAlias !== null) {
query.where('db_alias', dbAlias); query.where('db_alias', dbAlias);
} }
if (args?.condition) { if (args?.condition) {
query.where(args.condition); query.where(args.condition);
} }
@ -201,20 +221,27 @@ export default class NcMetaIOImpl extends NcMetaIO {
query.offset(args.offset); query.offset(args.offset);
} }
if (args?.xcCondition) { if (args?.xcCondition) {
(query as any).condition(args.xcCondition) (query as any).condition(args.xcCondition);
} }
if (args?.fields?.length) { if (args?.fields?.length) {
query.select(...args.fields) query.select(...args.fields);
} }
return query; return query;
} }
public async metaUpdate(project_id: string, dbAlias: string, target: string, data: any, idOrCondition?: string | { [p: string]: any }, xcCondition?): Promise<any> { public async metaUpdate(
project_id: string,
dbAlias: string,
target: string,
data: any,
idOrCondition?: string | { [p: string]: any },
xcCondition?
): Promise<any> {
const query = this.knexConnection(target); const query = this.knexConnection(target);
if (project_id !== null) { if (project_id !== null) {
query.where('project_id', project_id) query.where('project_id', project_id);
} }
if (dbAlias !== null) { if (dbAlias !== null) {
query.where('db_alias', dbAlias); query.where('db_alias', dbAlias);
@ -222,23 +249,25 @@ export default class NcMetaIOImpl extends NcMetaIO {
delete data.created_at; delete data.created_at;
query.update({...data, updated_at: this.knexConnection?.fn?.now()}); query.update({ ...data, updated_at: this.knexConnection?.fn?.now() });
if (typeof idOrCondition !== 'object') { if (typeof idOrCondition !== 'object') {
query.where('id', idOrCondition); query.where('id', idOrCondition);
} else if (idOrCondition) { } else if (idOrCondition) {
query.where(idOrCondition); query.where(idOrCondition);
} }
if (xcCondition) { if (xcCondition) {
query.condition(xcCondition) query.condition(xcCondition);
} }
// console.log(query.toQuery()) // console.log(query.toQuery())
return query; return query;
} }
public async metaDeleteAll(_project_id: string, _dbAlias: string): Promise<void> { public async metaDeleteAll(
_project_id: string,
_dbAlias: string
): Promise<void> {
// await this.knexConnection..dropTableIfExists('nc_roles').; // await this.knexConnection..dropTableIfExists('nc_roles').;
// await this.knexConnection.schema.dropTableIfExists('nc_store').; // await this.knexConnection.schema.dropTableIfExists('nc_store').;
// await this.knexConnection.schema.dropTableIfExists('nc_hooks').; // await this.knexConnection.schema.dropTableIfExists('nc_hooks').;
@ -246,10 +275,13 @@ export default class NcMetaIOImpl extends NcMetaIO {
// await this.knexConnection.schema.dropTableIfExists('nc_acl').; // await this.knexConnection.schema.dropTableIfExists('nc_acl').;
} }
public async isMetaDataExists(project_id: string, dbAlias: string): Promise<boolean> { public async isMetaDataExists(
project_id: string,
dbAlias: string
): Promise<boolean> {
const query = this.knexConnection('nc_models'); const query = this.knexConnection('nc_models');
if (project_id !== null) { if (project_id !== null) {
query.where('project_id', project_id) query.where('project_id', project_id);
} }
if (dbAlias !== null) { if (dbAlias !== null) {
query.where('db_alias', dbAlias); query.where('db_alias', dbAlias);
@ -279,31 +311,43 @@ export default class NcMetaIOImpl extends NcMetaIO {
} }
} }
async metaReset(project_id: string, dbAlias: string, apiType?: string): Promise<void> { async metaReset(
project_id: string,
dbAlias: string,
apiType?: string
): Promise<void> {
// const apiType: string = this.config?.envs?.[this.config.env || this.config.workingEnv]?.db.find(d => { // const apiType: string = this.config?.envs?.[this.config.env || this.config.workingEnv]?.db.find(d => {
// return d.meta.dbAlias === dbAlias; // return d.meta.dbAlias === dbAlias;
// })?.meta?.api?.type; // })?.meta?.api?.type;
if (apiType) { if (apiType) {
await Promise.all(META_TABLES?.[apiType]?.map(table => { await Promise.all(
return (async () => { META_TABLES?.[apiType]?.map(table => {
try { return (async () => {
await this.knexConnection(table).where({db_alias: dbAlias, project_id}).del(); try {
} catch (e) { await this.knexConnection(table)
console.warn(`Error: ${table} reset failed`) .where({ db_alias: dbAlias, project_id })
} .del();
})() } catch (e) {
})) console.warn(`Error: ${table} reset failed`);
} }
} })();
})
public async projectCreate(projectName: string, config: any, description?: string, );
meta?: boolean): Promise<any> { }
}
public async projectCreate(
projectName: string,
config: any,
description?: string,
meta?: boolean
): Promise<any> {
try { try {
const ranId = this.getNanoId(); const ranId = this.getNanoId();
const id = `${projectName.toLowerCase().replace(/\W+/g, '_')}_${ranId}`; const id = `${projectName.toLowerCase().replace(/\W+/g, '_')}_${ranId}`;
if (meta) { if (meta) {
config.prefix = `nc_${ranId}__` config.prefix = `nc_${ranId}__`;
// if(config.envs._noco?.db?.[0]?.meta?.tn){ // if(config.envs._noco?.db?.[0]?.meta?.tn){
// config.envs._noco.db[0].meta.tn += `_${prefix}` // config.envs._noco.db[0].meta.tn += `_${prefix}`
// } // }
@ -313,124 +357,180 @@ export default class NcMetaIOImpl extends NcMetaIO {
id, id,
title: projectName, title: projectName,
description, description,
config: CryptoJS.AES.encrypt(JSON.stringify(config), this.config?.auth?.jwt?.secret).toString() config: CryptoJS.AES.encrypt(
JSON.stringify(config),
this.config?.auth?.jwt?.secret
).toString()
}; };
// todo: check project name used or not // todo: check project name used or not
await this.knexConnection('nc_projects').insert({ await this.knexConnection('nc_projects').insert({
...project, ...project,
created_at: this.knexConnection?.fn?.now(), created_at: this.knexConnection?.fn?.now(),
updated_at: this.knexConnection?.fn?.now(), updated_at: this.knexConnection?.fn?.now()
}); });
return project; return project;
} catch (e) { } catch (e) {
console.log(e) console.log(e);
} }
} }
public async projectUpdate(projectId: string, public async projectUpdate(projectId: string, config: any): Promise<any> {
config: any): Promise<any> {
try { try {
const project = { const project = {
config: CryptoJS.AES.encrypt(JSON.stringify(config, null, 2), this.config?.auth?.jwt?.secret).toString() config: CryptoJS.AES.encrypt(
JSON.stringify(config, null, 2),
this.config?.auth?.jwt?.secret
).toString()
}; };
// todo: check project name used or not // todo: check project name used or not
await this.knexConnection('nc_projects').update(project).where({ await this.knexConnection('nc_projects')
id: projectId .update(project)
}); .where({
id: projectId
});
} catch (e) { } catch (e) {
console.log(e) console.log(e);
} }
} }
public async projectList(): Promise<any[]> { public async projectList(): Promise<any[]> {
return (await this.knexConnection('nc_projects').select()).map(p => { return (await this.knexConnection('nc_projects').select()).map(p => {
p.config = CryptoJS.AES.decrypt(p.config, this.config?.auth?.jwt?.secret).toString(CryptoJS.enc.Utf8) p.config = CryptoJS.AES.decrypt(
p.config,
this.config?.auth?.jwt?.secret
).toString(CryptoJS.enc.Utf8);
return p; return p;
}); });
} }
public async userProjectList(userId: any): Promise<any[]> { public async userProjectList(userId: any): Promise<any[]> {
return (
(
return (await this.knexConnection('nc_projects') await this.knexConnection('nc_projects')
.leftJoin(this.knexConnection('nc_projects_users') .leftJoin(
.where(`nc_projects_users.user_id`, userId).as('user'), 'user.project_id', 'nc_projects.id') this.knexConnection('nc_projects_users')
.select('nc_projects.*') .where(`nc_projects_users.user_id`, userId)
.select('user.user_id') .as('user'),
//(SELECT `xc_users`.`email` 'user.project_id',
// FROM `xc_users` 'nc_projects.id'
// INNER JOIN `nc_projects_users` )
// ON `nc_projects_users`.`user_id` = .select('nc_projects.*')
// `xc_users`.`id` and `nc_projects_users`.project_id=`nc_projects`.id where `nc_projects_users`.`roles` like '%owner%' limit 1) .select('user.user_id')
.select(this.knexConnection('xc_users') //(SELECT `xc_users`.`email`
.select('xc_users.email') // FROM `xc_users`
.innerJoin('nc_projects_users', 'nc_projects_users.user_id', '=', 'xc_users.id') // INNER JOIN `nc_projects_users`
.where('nc_projects_users.roles', 'like', '%owner%') // ON `nc_projects_users`.`user_id` =
.first() // `xc_users`.`id` and `nc_projects_users`.project_id=`nc_projects`.id where `nc_projects_users`.`roles` like '%owner%' limit 1)
.as('owner') .select(
) this.knexConnection('xc_users')
).map(p => { .select('xc_users.email')
p.allowed = p.user_id === userId; .innerJoin(
p.config = CryptoJS.AES.decrypt(p.config, this.config?.auth?.jwt?.secret).toString(CryptoJS.enc.Utf8) 'nc_projects_users',
return p; 'nc_projects_users.user_id',
}); '=',
} 'xc_users.id'
)
public async isUserHaveAccessToProject(projectId: string, userId: any): Promise<boolean> { .where('nc_projects_users.roles', 'like', '%owner%')
return !!(await this.knexConnection('nc_projects_users').where({ .first()
project_id: projectId, .as('owner')
user_id: userId )
}).first()); ).map(p => {
} p.allowed = p.user_id === userId;
p.config = CryptoJS.AES.decrypt(
public async projectGet(projectName: string, encrypt ?): Promise<any> { p.config,
const project = await this.knexConnection('nc_projects').where({ this.config?.auth?.jwt?.secret
title: projectName ).toString(CryptoJS.enc.Utf8);
}).first(); return p;
})
);
}
public async isUserHaveAccessToProject(
projectId: string,
userId: any
): Promise<boolean> {
return !!(await this.knexConnection('nc_projects_users')
.where({
project_id: projectId,
user_id: userId
})
.first());
}
public async projectGet(projectName: string, encrypt?): Promise<any> {
const project = await this.knexConnection('nc_projects')
.where({
title: projectName
})
.first();
if (project && !encrypt) { if (project && !encrypt) {
project.config = CryptoJS.AES.decrypt(project.config, this.config?.auth?.jwt?.secret).toString(CryptoJS.enc.Utf8) project.config = CryptoJS.AES.decrypt(
project.config,
this.config?.auth?.jwt?.secret
).toString(CryptoJS.enc.Utf8);
} }
return project; return project;
} }
public async projectGetById(projectId: string, encrypt ?): Promise<any> { public async projectGetById(projectId: string, encrypt?): Promise<any> {
const project = await this.knexConnection('nc_projects').where({ const project = await this.knexConnection('nc_projects')
id: projectId .where({
}).first(); id: projectId
})
.first();
if (project && !encrypt) { if (project && !encrypt) {
project.config = CryptoJS.AES.decrypt(project.config, this.config?.auth?.jwt?.secret).toString(CryptoJS.enc.Utf8) project.config = CryptoJS.AES.decrypt(
project.config,
this.config?.auth?.jwt?.secret
).toString(CryptoJS.enc.Utf8);
} }
return project; return project;
} }
public projectDelete(title: string): Promise<any> { public projectDelete(title: string): Promise<any> {
return this.knexConnection('nc_projects').where({ return this.knexConnection('nc_projects')
title .where({
}).delete(); title
})
.delete();
} }
public projectDeleteById(id: string): Promise<any> { public projectDeleteById(id: string): Promise<any> {
return this.knexConnection('nc_projects').where({ return this.knexConnection('nc_projects')
id .where({
}).delete(); id
} })
.delete();
public async projectStatusUpdate(projectName: string, status: string): Promise<any> { }
return this.knexConnection('nc_projects').update({
status public async projectStatusUpdate(
}).where({ projectName: string,
title: projectName status: string
}); ): Promise<any> {
return this.knexConnection('nc_projects')
.update({
status
})
.where({
title: projectName
});
} }
public async projectAddUser(projectId: string, userId: any, roles: string): Promise<any> { public async projectAddUser(
if (await this.knexConnection('nc_projects_users').where({ projectId: string,
user_id: userId, userId: any,
project_id: projectId roles: string
}).first()) { ): Promise<any> {
return {} if (
await this.knexConnection('nc_projects_users')
.where({
user_id: userId,
project_id: projectId
})
.first()
) {
return {};
} }
return this.knexConnection('nc_projects_users').insert({ return this.knexConnection('nc_projects_users').insert({
user_id: userId, user_id: userId,
@ -440,23 +540,30 @@ export default class NcMetaIOImpl extends NcMetaIO {
} }
public projectRemoveUser(projectId: string, userId: any): Promise<any> { public projectRemoveUser(projectId: string, userId: any): Promise<any> {
return this.knexConnection('nc_projects_users').where({ return this.knexConnection('nc_projects_users')
user_id: userId, .where({
project_id: projectId user_id: userId,
}).delete(); project_id: projectId
})
.delete();
} }
get isRest(): boolean { get isRest(): boolean {
return this.config?.envs?.[this.config.workingEnv]?.db?.some(db => db?.meta?.api?.type === 'rest'); return this.config?.envs?.[this.config.workingEnv]?.db?.some(
db => db?.meta?.api?.type === 'rest'
);
} }
get isGql(): boolean { get isGql(): boolean {
return this.config?.envs?.[this.config.workingEnv]?.db?.some(db => db?.meta?.api?.type === 'graphql'); return this.config?.envs?.[this.config.workingEnv]?.db?.some(
db => db?.meta?.api?.type === 'graphql'
);
} }
get isGrpc(): boolean { get isGrpc(): boolean {
return this.config?.envs?.[this.config.workingEnv]?.db?.some(db => db?.meta?.api?.type === 'grpc'); return this.config?.envs?.[this.config.workingEnv]?.db?.some(
db => db?.meta?.api?.type === 'grpc'
);
} }
public get knex(): any { public get knex(): any {
@ -464,17 +571,22 @@ export default class NcMetaIOImpl extends NcMetaIO {
} }
private getNanoId() { private getNanoId() {
return nanoid() return nanoid();
} }
public async audit(project_id: string, dbAlias: string, target: string, data: any): Promise<any> { public async audit(
project_id: string,
dbAlias: string,
target: string,
data: any
): Promise<any> {
if (['DATA', 'COMMENT'].includes(data?.op_type)) { if (['DATA', 'COMMENT'].includes(data?.op_type)) {
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
return this.metaInsert(project_id, dbAlias, target, data) return this.metaInsert(project_id, dbAlias, target, data);
} }
}
}/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>

12
packages/nocodb/src/lib/noco/meta/NcMetaIOImplEE.ts

@ -1,10 +1,16 @@
import NcMetaIOImpl from "./NcMetaIOImpl"; import NcMetaIOImpl from './NcMetaIOImpl';
export default class NcMetaIOImplEE extends NcMetaIOImpl { export default class NcMetaIOImplEE extends NcMetaIOImpl {
public async audit(project_id: string, dbAlias: string, target: string, data: any): Promise<any> { public async audit(
project_id: string,
dbAlias: string,
target: string,
data: any
): Promise<any> {
return this.metaInsert(project_id, dbAlias, target, data); return this.metaInsert(project_id, dbAlias, target, data);
} }
}/** }
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>

161
packages/nocodb/src/lib/noco/migrations/nc_001_init.ts

@ -1,9 +1,9 @@
// import s3 from "../plugins/s3"; // import s3 from "../plugins/s3";
// import gcs from "../plugins/gcs"; // import gcs from "../plugins/gcs";
// import smtp from "../plugins/smtp"; // import smtp from "../plugins/smtp";
import cache from "../plugins/cache"; import cache from '../plugins/cache';
import googleAuth from "../plugins/googleAuth"; import googleAuth from '../plugins/googleAuth';
import ses from "../plugins/ses"; import ses from '../plugins/ses';
// import azure from "../plugins/azure"; // import azure from "../plugins/azure";
// import brand from "../plugins/brand"; // import brand from "../plugins/brand";
// import discord from "../plugins/discord"; // import discord from "../plugins/discord";
@ -14,7 +14,7 @@ import ses from "../plugins/ses";
// import spaces from "../plugins/spaces"; // import spaces from "../plugins/spaces";
// import githubAuth from "../instant/common/plugins/githubAuth"; // import githubAuth from "../instant/common/plugins/githubAuth";
const up = async (knex) => { const up = async knex => {
await knex.schema.createTable('nc_projects', table => { await knex.schema.createTable('nc_projects', table => {
table.string('id', 128).primary(); table.string('id', 128).primary();
table.string('title'); table.string('title');
@ -23,8 +23,7 @@ const up = async (knex) => {
table.text('config'); table.text('config');
table.text('meta'); table.text('meta');
table.timestamps(); table.timestamps();
}) });
await knex.schema.createTable('nc_roles', table => { await knex.schema.createTable('nc_roles', table => {
table.increments(); table.increments();
@ -41,7 +40,8 @@ const up = async (knex) => {
db_alias: '', db_alias: '',
project_id: '', project_id: '',
title: 'owner', title: 'owner',
description: 'Can add/remove creators. And full edit database structures & fields.', description:
'Can add/remove creators. And full edit database structures & fields.',
type: 'SYSTEM' type: 'SYSTEM'
}, },
{ {
@ -55,7 +55,8 @@ const up = async (knex) => {
db_alias: '', db_alias: '',
project_id: '', project_id: '',
title: 'editor', title: 'editor',
description: 'Can edit records but cannot change structure of database/fields', description:
'Can edit records but cannot change structure of database/fields',
type: 'SYSTEM' type: 'SYSTEM'
}, },
{ {
@ -71,7 +72,7 @@ const up = async (knex) => {
title: 'viewer', title: 'viewer',
description: 'Can view the records but cannot edit anything', description: 'Can view the records but cannot edit anything',
type: 'SYSTEM' type: 'SYSTEM'
}, }
// { // {
// db_alias: '', // db_alias: '',
// project_id: '', // project_id: '',
@ -79,12 +80,12 @@ const up = async (knex) => {
// description: 'API access for an unauthorized user', // description: 'API access for an unauthorized user',
// type: 'SYSTEM' // type: 'SYSTEM'
// }, // },
]) ]);
await knex.schema.createTable('nc_hooks', table => { await knex.schema.createTable('nc_hooks', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias').defaultTo('db') table.string('db_alias').defaultTo('db');
table.string('title'); table.string('title');
table.string('description', 255); table.string('description', 255);
table.string('env').defaultTo('all'); table.string('env').defaultTo('all');
@ -107,28 +108,25 @@ const up = async (knex) => {
table.integer('timeout').defaultTo(60000); table.integer('timeout').defaultTo(60000);
table.boolean('active').defaultTo(true); table.boolean('active').defaultTo(true);
table.timestamps(); table.timestamps();
}) });
await knex('nc_hooks').insert({ await knex('nc_hooks').insert({
// url: 'http://localhost:4000/auth/hook', // url: 'http://localhost:4000/auth/hook',
type: 'AUTH_MIDDLEWARE' type: 'AUTH_MIDDLEWARE'
}) });
await knex.schema.createTable('nc_store', table => { await knex.schema.createTable('nc_store', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias').defaultTo('db') table.string('db_alias').defaultTo('db');
table.string('key').index(); table.string('key').index();
table.text('value', 'text'); table.text('value', 'text');
table.string('type'); table.string('type');
table.string('env'); table.string('env');
table.string('tag'); table.string('tag');
table.timestamps(); table.timestamps();
}) });
await knex('nc_store').insert({ await knex('nc_store').insert({
key: 'NC_DEBUG', key: 'NC_DEBUG',
@ -139,22 +137,21 @@ const up = async (knex) => {
'nc:api:gql': false, 'nc:api:gql': false,
'nc:api:grpc': false, 'nc:api:grpc': false,
'nc:migrator': false, 'nc:migrator': false,
'nc:datamapper': false, 'nc:datamapper': false
}), }),
db_alias: '' db_alias: ''
}) });
await knex('nc_store').insert({ await knex('nc_store').insert({
key: 'NC_PROJECT_COUNT', key: 'NC_PROJECT_COUNT',
value: '0', value: '0',
db_alias: '' db_alias: ''
}) });
await knex.schema.createTable('nc_cron', table => { await knex.schema.createTable('nc_cron', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias').defaultTo('db') table.string('db_alias').defaultTo('db');
table.string('title'); table.string('title');
table.string('description', 255); table.string('description', 255);
table.string('env'); table.string('env');
@ -170,30 +167,28 @@ const up = async (knex) => {
table.integer('timeout').defaultTo(60000); table.integer('timeout').defaultTo(60000);
table.timestamps(); table.timestamps();
}) });
await knex.schema.createTable('nc_acl', table => { await knex.schema.createTable('nc_acl', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias').defaultTo('db') table.string('db_alias').defaultTo('db');
table.string('tn'); table.string('tn');
table.text('acl'); table.text('acl');
table.string('type').defaultTo('table'); table.string('type').defaultTo('table');
table.timestamps(); table.timestamps();
}) });
await knex.schema.createTable('nc_models', table => { await knex.schema.createTable('nc_models', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias').defaultTo('db') table.string('db_alias').defaultTo('db');
table.string('title'); table.string('title');
table.string('alias'); table.string('alias');
table.string('type').defaultTo('table'); table.string('type').defaultTo('table');
table.text('meta', 'mediumtext'); table.text('meta', 'mediumtext');
table.text('schema', 'text'); table.text('schema', 'text');
table.text('schema_previous', 'text') table.text('schema_previous', 'text');
table.text('services', 'mediumtext'); table.text('services', 'mediumtext');
table.text('messages', 'text'); table.text('messages', 'text');
table.boolean('enabled').defaultTo(true); table.boolean('enabled').defaultTo(true);
@ -207,14 +202,13 @@ const up = async (knex) => {
table.boolean('pinned'); table.boolean('pinned');
table.timestamps(); table.timestamps();
table.index(['db_alias', 'title']) table.index(['db_alias', 'title']);
}) });
await knex.schema.createTable('nc_relations', table => { await knex.schema.createTable('nc_relations', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias') table.string('db_alias');
table.string('tn'); table.string('tn');
table.string('rtn'); table.string('rtn');
table.string('_tn'); table.string('_tn');
@ -230,13 +224,13 @@ const up = async (knex) => {
table.string('dr'); table.string('dr');
table.timestamps(); table.timestamps();
table.index(['db_alias', 'tn']) table.index(['db_alias', 'tn']);
}) });
await knex.schema.createTable('nc_routes', table => { await knex.schema.createTable('nc_routes', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias').defaultTo('db') table.string('db_alias').defaultTo('db');
table.string('title'); table.string('title');
table.string('tn'); table.string('tn');
table.string('tnp'); table.string('tnp');
@ -252,13 +246,13 @@ const up = async (knex) => {
table.boolean('is_custom'); table.boolean('is_custom');
// table.text('placeholder', 'longtext'); // table.text('placeholder', 'longtext');
table.timestamps(); table.timestamps();
table.index(['db_alias', 'title', 'tn']) table.index(['db_alias', 'title', 'tn']);
}) });
await knex.schema.createTable('nc_resolvers', (table) => { await knex.schema.createTable('nc_resolvers', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias').defaultTo('db') table.string('db_alias').defaultTo('db');
table.string('title'); table.string('title');
table.text('resolver', 'text'); table.text('resolver', 'text');
table.string('type'); table.string('type');
@ -267,12 +261,12 @@ const up = async (knex) => {
table.integer('handler_type').defaultTo(1); table.integer('handler_type').defaultTo(1);
// table.text('placeholder', 'text'); // table.text('placeholder', 'text');
table.timestamps(); table.timestamps();
}) });
await knex.schema.createTable('nc_loaders', table => { await knex.schema.createTable('nc_loaders', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias').defaultTo('db') table.string('db_alias').defaultTo('db');
table.string('title'); table.string('title');
table.string('parent'); table.string('parent');
table.string('child'); table.string('child');
@ -280,12 +274,12 @@ const up = async (knex) => {
table.string('resolver'); table.string('resolver');
table.text('functions'); table.text('functions');
table.timestamps(); table.timestamps();
}) });
await knex.schema.createTable('nc_rpc', (table) => { await knex.schema.createTable('nc_rpc', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias').defaultTo('db') table.string('db_alias').defaultTo('db');
table.string('title'); table.string('title');
table.string('tn'); table.string('tn');
table.text('service', 'text'); table.text('service', 'text');
@ -301,17 +295,20 @@ const up = async (knex) => {
table.integer('handler_type').defaultTo(1); table.integer('handler_type').defaultTo(1);
// table.text('placeholder', 'text'); // table.text('placeholder', 'text');
table.timestamps(); table.timestamps();
}) });
await knex.schema.createTable('nc_projects_users', (table) => { await knex.schema.createTable('nc_projects_users', table => {
table.string('project_id').index() // .references('id').inTable('nc_projects') table.string('project_id').index(); // .references('id').inTable('nc_projects')
// todo: foreign key // todo: foreign key
table.integer('user_id').unsigned().index()//.references('id').inTable('xc_users') table
.integer('user_id')
.unsigned()
.index(); //.references('id').inTable('xc_users')
table.text('roles'); table.text('roles');
// table.text('placeholder', 'text'); // table.text('placeholder', 'text');
table.timestamps(); table.timestamps();
}) });
await knex.schema.createTable('nc_shared_views', (table) => { await knex.schema.createTable('nc_shared_views', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias'); table.string('db_alias');
@ -323,9 +320,9 @@ const up = async (knex) => {
table.boolean('allow_copy'); table.boolean('allow_copy');
table.string('password'); table.string('password');
table.timestamps(); table.timestamps();
}) });
await knex.schema.createTable('nc_disabled_models_for_role', (table) => { await knex.schema.createTable('nc_disabled_models_for_role', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias', 45); table.string('db_alias', 45);
@ -340,10 +337,13 @@ const up = async (knex) => {
table.string('rcn'); table.string('rcn');
table.string('relation_type'); table.string('relation_type');
table.timestamps(); table.timestamps();
table.index(['project_id', 'db_alias', 'title', 'type', 'role'], 'xc_disabled124_idx'); table.index(
}) ['project_id', 'db_alias', 'title', 'type', 'role'],
'xc_disabled124_idx'
);
});
await knex.schema.createTable('nc_plugins', (table) => { await knex.schema.createTable('nc_plugins', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias'); table.string('db_alias');
@ -365,18 +365,17 @@ const up = async (knex) => {
table.string('creator_website'); table.string('creator_website');
table.string('price'); table.string('price');
table.timestamps(); table.timestamps();
}) });
await knex('nc_plugins').insert([ await knex('nc_plugins').insert([
googleAuth, googleAuth,
ses, ses,
cache, cache
// ee, // ee,
// brand, // brand,
]) ]);
await knex.schema.createTable('nc_audit', (table) => { await knex.schema.createTable('nc_audit', table => {
table.increments(); table.increments();
table.string('user'); table.string('user');
table.string('ip'); table.string('ip');
@ -390,31 +389,32 @@ const up = async (knex) => {
table.string('status'); table.string('status');
table.text('description'); table.text('description');
table.text('details'); table.text('details');
table.index(['db_alias', 'project_id', 'model_name', 'model_id'], '`nc_audit_index`') table.index(
['db_alias', 'project_id', 'model_name', 'model_id'],
'`nc_audit_index`'
);
table.timestamps(); table.timestamps();
}) });
await knex.schema.createTable('nc_migrations', (table) => { await knex.schema.createTable('nc_migrations', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias'); table.string('db_alias');
table.text('up'); table.text('up');
table.text('down'); table.text('down');
table.string('title').notNullable();
table.string("title").notNullable(); table.string('title_down').nullable();
table.string("title_down").nullable(); table.string('description').nullable();
table.string("description").nullable(); table.integer('batch').nullable();
table.integer("batch").nullable(); table.string('checksum').nullable();
table.string("checksum").nullable(); table.integer('status').nullable();
table.integer("status").nullable();
table.timestamps(); table.timestamps();
}) });
await knex.schema.createTable('nc_api_tokens', (table) => { await knex.schema.createTable('nc_api_tokens', table => {
table.increments(); table.increments();
table.string('project_id'); table.string('project_id');
table.string('db_alias'); table.string('db_alias');
@ -424,12 +424,10 @@ const up = async (knex) => {
table.string('expiry'); table.string('expiry');
table.boolean('enabled').defaultTo(true); table.boolean('enabled').defaultTo(true);
table.timestamps(); table.timestamps();
}) });
}; };
const down = async (knex) => { const down = async knex => {
await knex.schema.dropTable('nc_plugins'); await knex.schema.dropTable('nc_plugins');
await knex.schema.dropTable('nc_disabled_models_for_role'); await knex.schema.dropTable('nc_disabled_models_for_role');
await knex.schema.dropTable('nc_shared_views'); await knex.schema.dropTable('nc_shared_views');
@ -451,7 +449,4 @@ const down = async (knex) => {
await knex.schema.dropTable('nc_api_tokens'); await knex.schema.dropTable('nc_api_tokens');
}; };
export { up, down };
export {
up, down
}

58
packages/nocodb/src/lib/noco/migrations/nc_002_add_m2m.ts

@ -1,43 +1,39 @@
import Knex from "knex"; import Knex from 'knex';
const up = async (knex: Knex) => { const up = async (knex: Knex) => {
await knex.schema.alterTable('nc_models', table => { await knex.schema.alterTable('nc_models', table => {
table.integer('mm'); table.integer('mm');
table.text('m_to_m_meta'); table.text('m_to_m_meta');
}) });
}; };
const down = async (knex) => { const down = async knex => {
await knex.schema.alterTable('nc_models', table => { await knex.schema.alterTable('nc_models', table => {
table.dropColumns('mm', 'm_to_m_meta'); table.dropColumns('mm', 'm_to_m_meta');
}) });
}; };
export { up, down };
export { /**
up, down * @copyright Copyright (c) 2021, Xgene Cloud Ltd
} *
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
/** *
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @license GNU AGPL version 3 or any later version
* *
* @author Naveen MR <oof1lab@gmail.com> * This program is free software: you can redistribute it and/or modify
* @author Pranav C Balan <pranavxc@gmail.com> * it under the terms of the GNU Affero General Public License as
* * published by the Free Software Foundation, either version 3 of the
* @license GNU AGPL version 3 or any later version * License, or (at your option) any later version.
* *
* This program is free software: you can redistribute it and/or modify * This program is distributed in the hope that it will be useful,
* it under the terms of the GNU Affero General Public License as * but WITHOUT ANY WARRANTY; without even the implied warranty of
* published by the Free Software Foundation, either version 3 of the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* License, or (at your option) any later version. * GNU Affero General Public License for more details.
* *
* This program is distributed in the hope that it will be useful, * You should have received a copy of the GNU Affero General Public License
* but WITHOUT ANY WARRANTY; without even the implied warranty of * along with this program. If not, see <http://www.gnu.org/licenses/>.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. */
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

14
packages/nocodb/src/lib/noco/migrations/nc_003_add_fkn_column.ts

@ -1,22 +1,18 @@
import Knex from "knex"; import Knex from 'knex';
const up = async (knex: Knex) => { const up = async (knex: Knex) => {
await knex.schema.alterTable('nc_relations', table => { await knex.schema.alterTable('nc_relations', table => {
table.string('fkn'); table.string('fkn');
}) });
}; };
const down = async (knex) => { const down = async knex => {
await knex.schema.alterTable('nc_relations', table => { await knex.schema.alterTable('nc_relations', table => {
table.dropColumns('fkn'); table.dropColumns('fkn');
}) });
}; };
export { up, down };
export {
up, down
}
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd

43
packages/nocodb/src/lib/noco/nc.try.ts

@ -1,13 +1,11 @@
import cors from 'cors'; import cors from 'cors';
import express from 'express'; import express from 'express';
import NcConfigFactory from "../utils/NcConfigFactory"; import NcConfigFactory from '../utils/NcConfigFactory';
import Noco from "./Noco";
import Noco from './Noco';
export default async function (dbUrl): Promise<void> { export default async function(dbUrl): Promise<void> {
const server = express(); const server = express();
server.use(cors()); server.use(cors());
@ -15,22 +13,31 @@ export default async function (dbUrl): Promise<void> {
process.env[`NC_TRY`] = 'true'; process.env[`NC_TRY`] = 'true';
(async () => { (async () => {
const app = new Noco(); const app = new Noco();
server.use(await app.init({ server.use(
async afterMetaMigrationInit(): Promise<void> { await app.init({
async afterMetaMigrationInit(): Promise<void> {
const config = NcConfigFactory.makeProjectConfigFromUrl(dbUrl); const config = NcConfigFactory.makeProjectConfigFromUrl(dbUrl);
await app.ncMeta.projectCreate('Dvdrental (Sample SQLite Database)', config, ''); await app.ncMeta.projectCreate(
await app.ncMeta.projectStatusUpdate('Dvdrental (Sample SQLite Database)', 'started'); 'Dvdrental (Sample SQLite Database)',
config,
} ''
})); );
await app.ncMeta.projectStatusUpdate(
'Dvdrental (Sample SQLite Database)',
'started'
);
}
})
);
server.listen(process.env.PORT || 8080, () => { server.listen(process.env.PORT || 8080, () => {
console.log(`App started successfully.\nVisit -> http://localhost:${process.env.PORT || 8080}/xc`); console.log(
}) `App started successfully.\nVisit -> http://localhost:${process.env
})().catch(e => console.log(e)) .PORT || 8080}/xc`
);
});
})().catch(e => console.log(e));
} }
/** /**

111
packages/nocodb/src/lib/noco/plugins/NcPluginMgr.ts

@ -4,31 +4,32 @@ import {
IWebhookNotificationAdapter, IWebhookNotificationAdapter,
XcEmailPlugin, XcEmailPlugin,
XcPlugin, XcPlugin,
XcStoragePlugin, XcWebhookNotificationPlugin XcStoragePlugin,
} from "nc-plugin"; XcWebhookNotificationPlugin
} from 'nc-plugin';
import BackblazePluginConfig from "../../../plugins/backblaze";
import DiscordPluginConfig from "../../../plugins/discord"; import BackblazePluginConfig from '../../../plugins/backblaze';
import GcsPluginConfig from "../../../plugins/gcs"; import DiscordPluginConfig from '../../../plugins/discord';
import LinodePluginConfig from "../../../plugins/linode"; import GcsPluginConfig from '../../../plugins/gcs';
import MattermostPluginConfig from "../../../plugins/mattermost"; import LinodePluginConfig from '../../../plugins/linode';
import MinioPluginConfig from "../../../plugins/mino"; import MattermostPluginConfig from '../../../plugins/mattermost';
import OvhCloudPluginConfig from "../../../plugins/ovhCloud"; import MinioPluginConfig from '../../../plugins/mino';
import S3PluginConfig from "../../../plugins/s3"; import OvhCloudPluginConfig from '../../../plugins/ovhCloud';
import ScalewayPluginConfig from "../../../plugins/scaleway"; import S3PluginConfig from '../../../plugins/s3';
import SlackPluginConfig from "../../../plugins/slack"; import ScalewayPluginConfig from '../../../plugins/scaleway';
import SMTPPluginConfig from "../../../plugins/smtp"; import SlackPluginConfig from '../../../plugins/slack';
import MailerSendConfig from "../../../plugins/mailerSend"; import SMTPPluginConfig from '../../../plugins/smtp';
import SpacesPluginConfig from "../../../plugins/spaces"; import MailerSendConfig from '../../../plugins/mailerSend';
import TeamsPluginConfig from "../../../plugins/teams"; import SpacesPluginConfig from '../../../plugins/spaces';
import TwilioPluginConfig from "../../../plugins/twilio"; import TeamsPluginConfig from '../../../plugins/teams';
import TwilioWhatsappPluginConfig from "../../../plugins/twilioWhatsapp"; import TwilioPluginConfig from '../../../plugins/twilio';
import UpcloudPluginConfig from "../../../plugins/upcloud"; import TwilioWhatsappPluginConfig from '../../../plugins/twilioWhatsapp';
import VultrPluginConfig from "../../../plugins/vultr"; import UpcloudPluginConfig from '../../../plugins/upcloud';
import Noco from "../Noco"; import VultrPluginConfig from '../../../plugins/vultr';
import NcMetaIO from "../meta/NcMetaIO"; import Noco from '../Noco';
import NcMetaIO from '../meta/NcMetaIO';
import Local from "./adapters/storage/Local";
import Local from './adapters/storage/Local';
const defaultPlugins = [ const defaultPlugins = [
SlackPluginConfig, SlackPluginConfig,
@ -48,16 +49,15 @@ const defaultPlugins = [
UpcloudPluginConfig, UpcloudPluginConfig,
SMTPPluginConfig, SMTPPluginConfig,
MailerSendConfig, MailerSendConfig,
ScalewayPluginConfig, ScalewayPluginConfig
] ];
class NcPluginMgr { class NcPluginMgr {
private ncMeta: NcMetaIO; private ncMeta: NcMetaIO;
private app: Noco; private app: Noco;
/* active plugins */ /* active plugins */
private activePlugins: Array<XcPlugin | XcStoragePlugin | XcEmailPlugin> private activePlugins: Array<XcPlugin | XcStoragePlugin | XcEmailPlugin>;
constructor(app: Noco, ncMeta: NcMetaIO) { constructor(app: Noco, ncMeta: NcMetaIO) {
this.app = app; this.app = app;
@ -66,16 +66,13 @@ class NcPluginMgr {
} }
public async init(): Promise<void> { public async init(): Promise<void> {
/* Populate rows into nc_plugins table if not present */ /* Populate rows into nc_plugins table if not present */
for (const plugin of defaultPlugins) { for (const plugin of defaultPlugins) {
const pluginConfig = await this.ncMeta.metaGet(null, null, 'nc_plugins', {
const pluginConfig = (await this.ncMeta.metaGet(null, null, 'nc_plugins', {
title: plugin.title title: plugin.title
})); });
if (!pluginConfig) { if (!pluginConfig) {
await this.ncMeta.metaInsert(null, null, 'nc_plugins', { await this.ncMeta.metaInsert(null, null, 'nc_plugins', {
title: plugin.title, title: plugin.title,
version: plugin.version, version: plugin.version,
@ -85,12 +82,10 @@ class NcPluginMgr {
category: plugin.category, category: plugin.category,
input_schema: JSON.stringify(plugin.inputs) input_schema: JSON.stringify(plugin.inputs)
}); });
} }
/* init only the active plugins */ /* init only the active plugins */
if (pluginConfig?.active) { if (pluginConfig?.active) {
const tempPlugin = new plugin.builder(this.app, plugin); const tempPlugin = new plugin.builder(this.app, plugin);
this.activePlugins.push(tempPlugin); this.activePlugins.push(tempPlugin);
@ -102,52 +97,62 @@ class NcPluginMgr {
try { try {
await tempPlugin.init(pluginConfig?.input); await tempPlugin.init(pluginConfig?.input);
} catch (e) { } catch (e) {
console.log(`Plugin(${plugin?.title}) initialization failed : ${e.message}`) console.log(
`Plugin(${plugin?.title}) initialization failed : ${e.message}`
);
} }
} }
} }
} }
public async reInit(): Promise<void> { public async reInit(): Promise<void> {
this.activePlugins = []; this.activePlugins = [];
await this.init(); await this.init();
} }
public get storageAdapter(): IStorageAdapter { public get storageAdapter(): IStorageAdapter {
return (this.activePlugins?.find(plugin => plugin instanceof XcStoragePlugin) as XcStoragePlugin)?.getAdapter() || new Local(); return (
(this.activePlugins?.find(
plugin => plugin instanceof XcStoragePlugin
) as XcStoragePlugin)?.getAdapter() || new Local()
);
} }
public get emailAdapter(): IEmailAdapter { public get emailAdapter(): IEmailAdapter {
return (this.activePlugins?.find(plugin => plugin instanceof XcEmailPlugin) as XcEmailPlugin)?.getAdapter(); return (this.activePlugins?.find(
plugin => plugin instanceof XcEmailPlugin
) as XcEmailPlugin)?.getAdapter();
} }
public get webhookNotificationAdapters(): { [key: string]: IWebhookNotificationAdapter } { public get webhookNotificationAdapters(): {
[key: string]: IWebhookNotificationAdapter;
} {
return this.activePlugins?.reduce((obj, plugin) => { return this.activePlugins?.reduce((obj, plugin) => {
if (plugin instanceof XcWebhookNotificationPlugin) { if (plugin instanceof XcWebhookNotificationPlugin) {
obj[plugin?.config?.title] = (plugin as XcWebhookNotificationPlugin)?.getAdapter() obj[
plugin?.config?.title
] = (plugin as XcWebhookNotificationPlugin)?.getAdapter();
} }
return obj; return obj;
}, {}); }, {});
} }
public async test(args: any): Promise<boolean> { public async test(args: any): Promise<boolean> {
switch (args.category) { switch (args.category) {
case 'Storage': { case 'Storage':
const plugin = defaultPlugins.find(pluginConfig => pluginConfig?.title === args.title); {
const tempPlugin = new plugin.builder(this.app, plugin); const plugin = defaultPlugins.find(
await tempPlugin.init(args?.input); pluginConfig => pluginConfig?.title === args.title
return tempPlugin?.getAdapter()?.test?.(); );
} const tempPlugin = new plugin.builder(this.app, plugin);
await tempPlugin.init(args?.input);
return tempPlugin?.getAdapter()?.test?.();
}
break; break;
default: default:
throw new Error('Not implemented'); throw new Error('Not implemented');
} }
} }
} }
export default NcPluginMgr; export default NcPluginMgr;
@ -173,4 +178,4 @@ export default NcPluginMgr;
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */

13
packages/nocodb/src/lib/noco/plugins/adapters/cache/XcCache.ts vendored

@ -1,9 +1,7 @@
import LRU from 'lru-cache'; import LRU from 'lru-cache';
export default class XcCache { export default class XcCache {
public static init(config: any, overwrite = false) { public static init(config: any, overwrite = false) {
if (overwrite && this.instance) { if (overwrite && this.instance) {
this.instance.reset(); this.instance.reset();
this.instance = null; this.instance = null;
@ -11,18 +9,18 @@ export default class XcCache {
if (!this.instance) { if (!this.instance) {
const options = { const options = {
max: 500, maxAge: 1000 * 60 * 60 max: 500,
} maxAge: 1000 * 60 * 60
};
if (config) { if (config) {
const input = JSON.parse(config.input); const input = JSON.parse(config.input);
Object.assign(options, input); Object.assign(options, input);
} }
this.instance = new LRU(options) this.instance = new LRU(options);
} }
} }
public static get(key): any { public static get(key): any {
return this.instance?.get(key); return this.instance?.get(key);
} }
@ -36,5 +34,4 @@ export default class XcCache {
} }
private static instance: LRU<any, any>; private static instance: LRU<any, any>;
}
}

21
packages/nocodb/src/lib/noco/plugins/adapters/discord/Discord.ts

@ -1,19 +1,20 @@
import axios from 'axios'; import axios from 'axios';
export default class Discord { export default class Discord {
public static async sendMessage(
public static async sendMessage(content: string, webhooks: Array<{ content: string,
webhook_url: string webhooks: Array<{
}>): Promise<any> { webhook_url: string;
for (const {webhook_url} of webhooks) { }>
): Promise<any> {
for (const { webhook_url } of webhooks) {
try { try {
await axios.post(webhook_url, { await axios.post(webhook_url, {
content content
}) });
}catch(e){ } catch (e) {
console.log(e) console.log(e);
} }
} }
} }
}
}

12
packages/nocodb/src/lib/noco/plugins/adapters/email/EmailFactory.ts

@ -1,10 +1,9 @@
import IEmailAdapter from "../../../../../interface/IEmailAdapter"; import IEmailAdapter from '../../../../../interface/IEmailAdapter';
import SES from "./SES"; import SES from './SES';
import SMTP from "./SMTP"; import SMTP from './SMTP';
export default class EmailFactory { export default class EmailFactory {
private static instance: IEmailAdapter; private static instance: IEmailAdapter;
// tslint:disable-next-line:typedef // tslint:disable-next-line:typedef
@ -34,12 +33,8 @@ export default class EmailFactory {
break; break;
} }
} }
} }
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
@ -62,4 +57,3 @@ export default class EmailFactory {
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */

18
packages/nocodb/src/lib/noco/plugins/adapters/email/SES.ts

@ -1,30 +1,22 @@
// @ts-ignore // @ts-ignore
import IEmailAdapter, {XcEmail} from "../../../../../interface/IEmailAdapter"; import IEmailAdapter, { XcEmail } from '../../../../../interface/IEmailAdapter';
export default // @ts-ignore export default // @ts-ignore
class SES implements IEmailAdapter { class SES implements IEmailAdapter {
// @ts-ignore // @ts-ignore
private input: any; private input: any;
constructor(input:any) { constructor(input: any) {
this.input= input; this.input = input;
}
public async init(): Promise<any> {
} }
public async mailSend(_mail:XcEmail): Promise<any> { public async init(): Promise<any> {}
} public async mailSend(_mail: XcEmail): Promise<any> {}
test(_email): Promise<boolean> { test(_email): Promise<boolean> {
return Promise.resolve(false); return Promise.resolve(false);
} }
} }
/** /**

27
packages/nocodb/src/lib/noco/plugins/adapters/email/SMTP.ts

@ -1,12 +1,11 @@
// @ts-ignore // @ts-ignore
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import Mail from "nodemailer/lib/mailer"; import Mail from 'nodemailer/lib/mailer';
import IEmailAdapter, {XcEmail} from "../../../../../interface/IEmailAdapter"; import IEmailAdapter, { XcEmail } from '../../../../../interface/IEmailAdapter';
export default // @ts-ignore export default // @ts-ignore
class SMTP implements IEmailAdapter { class SMTP implements IEmailAdapter {
private transporter: Mail; private transporter: Mail;
private input: any; private input: any;
@ -18,22 +17,22 @@ class SMTP implements IEmailAdapter {
const config = { const config = {
// from: this.input.from, // from: this.input.from,
// options: { // options: {
"host": this.input?.host, host: this.input?.host,
"port": parseInt(this.input?.port, 10), port: parseInt(this.input?.port, 10),
"secure": this.input?.secure === 'true', secure: this.input?.secure === 'true',
"ignoreTLS": this.input?.ignoreTLS === 'true', ignoreTLS: this.input?.ignoreTLS === 'true',
"auth": { auth: {
"user": this.input?.username, user: this.input?.username,
"pass": this.input?.password pass: this.input?.password
} }
// } // }
} };
this.transporter = nodemailer.createTransport(config); this.transporter = nodemailer.createTransport(config);
} }
public async mailSend(mail: XcEmail): Promise<any> { public async mailSend(mail: XcEmail): Promise<any> {
if (this.transporter) { if (this.transporter) {
await this.transporter.sendMail({...mail, from: this.input.from}) await this.transporter.sendMail({ ...mail, from: this.input.from });
} }
} }
@ -41,9 +40,9 @@ class SMTP implements IEmailAdapter {
try { try {
this.mailSend({ this.mailSend({
to: email, to: email,
subject: "Test email", subject: 'Test email',
html: 'Test email' html: 'Test email'
} as any) } as any);
return true; return true;
} catch (e) { } catch (e) {
throw e; throw e;

21
packages/nocodb/src/lib/noco/plugins/adapters/mattermost/Mattermost.ts

@ -1,19 +1,20 @@
import axios from 'axios'; import axios from 'axios';
export default class Mattermost { export default class Mattermost {
public static async sendMessage(
public static async sendMessage(text: string, webhooks: Array<{ text: string,
webhook_url: string webhooks: Array<{
}>): Promise<any> { webhook_url: string;
for (const {webhook_url} of webhooks) { }>
): Promise<any> {
for (const { webhook_url } of webhooks) {
try { try {
await axios.post(webhook_url, { await axios.post(webhook_url, {
text text
}) });
}catch(e){ } catch (e) {
console.log(e) console.log(e);
} }
} }
} }
}
}

19
packages/nocodb/src/lib/noco/plugins/adapters/slack/Slack.ts

@ -1,19 +1,20 @@
import axios from 'axios'; import axios from 'axios';
export default class Slack { export default class Slack {
public static async sendMessage(
public static async sendMessage(text: string, webhooks: Array<{ text: string,
webhook_url: string webhooks: Array<{
}>): Promise<any> { webhook_url: string;
for (const {webhook_url} of webhooks) { }>
): Promise<any> {
for (const { webhook_url } of webhooks) {
try { try {
await axios.post(webhook_url, { await axios.post(webhook_url, {
text text
}) });
} catch (e) { } catch (e) {
console.log(e) console.log(e);
} }
} }
} }
}
}

24
packages/nocodb/src/lib/noco/plugins/adapters/storage/Local.ts

@ -1,15 +1,15 @@
import fs from "fs"; import fs from 'fs';
import path from "path"; import path from 'path';
import mkdirp from "mkdirp"; import mkdirp from 'mkdirp';
import IStorageAdapter, {XcFile} from "../../../../../interface/IStorageAdapter"; import IStorageAdapter, {
import NcConfigFactory from "../../../../utils/NcConfigFactory"; XcFile
} from '../../../../../interface/IStorageAdapter';
import NcConfigFactory from '../../../../utils/NcConfigFactory';
export default class Local implements IStorageAdapter { export default class Local implements IStorageAdapter {
constructor() {}
constructor() {
}
public async fileCreate(key: string, file: XcFile): Promise<any> { public async fileCreate(key: string, file: XcFile): Promise<any> {
const destPath = path.join(NcConfigFactory.getToolDir(), ...key.split('/')); const destPath = path.join(NcConfigFactory.getToolDir(), ...key.split('/'));
@ -28,7 +28,9 @@ export default class Local implements IStorageAdapter {
public async fileRead(filePath: string): Promise<any> { public async fileRead(filePath: string): Promise<any> {
try { try {
const fileData = await fs.promises.readFile(path.join(NcConfigFactory.getToolDir(), ...filePath.split('/'))); const fileData = await fs.promises.readFile(
path.join(NcConfigFactory.getToolDir(), ...filePath.split('/'))
);
return fileData; return fileData;
} catch (e) { } catch (e) {
throw e; throw e;
@ -42,8 +44,8 @@ export default class Local implements IStorageAdapter {
test(): Promise<boolean> { test(): Promise<boolean> {
return Promise.resolve(false); return Promise.resolve(false);
} }
}
}/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>

20
packages/nocodb/src/lib/noco/plugins/adapters/twilio/Twilio.ts

@ -1,7 +1,6 @@
import twilio from "twilio"; import twilio from 'twilio';
export default class Twilio { export default class Twilio {
private static instance: Twilio; private static instance: Twilio;
private input: any; private input: any;
@ -17,7 +16,6 @@ export default class Twilio {
// tslint:disable-next-line:typedef // tslint:disable-next-line:typedef
public static create(config: any, overwrite = false): Twilio { public static create(config: any, overwrite = false): Twilio {
if (this.instance && !overwrite) { if (this.instance && !overwrite) {
return this.instance; return this.instance;
} }
@ -32,16 +30,14 @@ export default class Twilio {
public async sendMessage(content: string, numbers: string[]): Promise<any> { public async sendMessage(content: string, numbers: string[]): Promise<any> {
for (const num of numbers) { for (const num of numbers) {
try { try {
await this.client.messages await this.client.messages.create({
.create({ body: content,
body: content, from: this.input.from,
from: this.input.from, to: num
to: num });
})
} catch (e) { } catch (e) {
console.log(e) console.log(e);
} }
} }
} }
}
}

84
packages/nocodb/src/lib/noco/plugins/azure.ts

@ -1,52 +1,58 @@
import {XcActionType, XcForm, XcType} from "nc-common"; import { XcActionType, XcForm, XcType } from 'nc-common';
const input: XcForm = { const input: XcForm = {
title: 'Configure Azure Storage', title: 'Configure Azure Storage',
items: [{ items: [
key: 'account', {
label: 'Azure Account Name', key: 'account',
placeholder: 'Azure Account Name', label: 'Azure Account Name',
type: XcType.SingleLineText, placeholder: 'Azure Account Name',
required: true type: XcType.SingleLineText,
}, { required: true
key: 'container', },
label: 'Storage Container', {
placeholder: 'Storage Container', key: 'container',
type: XcType.SingleLineText, label: 'Storage Container',
required: true placeholder: 'Storage Container',
}, { type: XcType.SingleLineText,
key: 'access_key', required: true
label: 'Access Key', },
placeholder: 'Access Key', {
type: XcType.Password, key: 'access_key',
required: true label: 'Access Key',
}], placeholder: 'Access Key',
actions: [{ type: XcType.Password,
label: 'Test', required: true
placeholder: 'Test', }
key: 'test', ],
actionType: XcActionType.TEST, actions: [
type: XcType.Button {
}, { label: 'Test',
label: 'Save', placeholder: 'Test',
placeholder: 'Save', key: 'test',
key: 'save', actionType: XcActionType.TEST,
actionType: XcActionType.SUBMIT, type: XcType.Button
type: XcType.Button },
},], {
msgOnInstall:'Successfully installed and attachment will be stored in Azure', label: 'Save',
msgOnUninstall:'', placeholder: 'Save',
key: 'save',
actionType: XcActionType.SUBMIT,
type: XcType.Button
}
],
msgOnInstall: 'Successfully installed and attachment will be stored in Azure',
msgOnUninstall: ''
}; };
export default { export default {
title: 'Azure', title: 'Azure',
version: '0.0.1', version: '0.0.1',
logo: 'plugins/azure.png', logo: 'plugins/azure.png',
description: 'Azure Blob storage is Microsoft\'s object storage solution for the cloud. Blob storage is optimized for storing massive amounts of unstructured data, such as text or binary data.', description:
"Azure Blob storage is Microsoft's object storage solution for the cloud. Blob storage is optimized for storing massive amounts of unstructured data, such as text or binary data.",
price: 'Free', price: 'Free',
tags: 'Storage', tags: 'Storage',
category: 'Storage', category: 'Storage',
input_schema: JSON.stringify(input) input_schema: JSON.stringify(input)
}; };

35
packages/nocodb/src/lib/noco/plugins/brand.ts

@ -1,4 +1,4 @@
import {XcActionType, XcForm, XcType} from "nc-common"; import { XcActionType, XcForm, XcType } from 'nc-common';
const input: XcForm = { const input: XcForm = {
title: 'Branding', title: 'Branding',
@ -9,13 +9,15 @@ const input: XcForm = {
placeholder: 'Title', placeholder: 'Title',
type: XcType.SingleLineText, type: XcType.SingleLineText,
required: true required: true
}, { },
{
key: 'logo', key: 'logo',
label: 'Logo', label: 'Logo',
placeholder: 'Logo', placeholder: 'Logo',
type: XcType.Attachment, type: XcType.Attachment,
required: true required: true
}, { },
{
key: 'favicon', key: 'favicon',
label: 'Favicon', label: 'Favicon',
placeholder: 'Favicon', placeholder: 'Favicon',
@ -49,18 +51,21 @@ const input: XcForm = {
placeholder: 'Youtube', placeholder: 'Youtube',
type: XcType.URL, type: XcType.URL,
required: false required: false
},], }
actions: [{ ],
label: 'Save', actions: [
key: 'save', {
actionType: XcActionType.SUBMIT, label: 'Save',
type: XcType.Button key: 'save',
},], actionType: XcActionType.SUBMIT,
msgOnInstall: 'Successfully installed and hard refresh the browser to reflect the changes', type: XcType.Button
msgOnUninstall: '', }
],
msgOnInstall:
'Successfully installed and hard refresh the browser to reflect the changes',
msgOnUninstall: ''
}; };
export default { export default {
title: 'Branding', title: 'Branding',
version: '0.0.1', version: '0.0.1',
@ -69,5 +74,5 @@ export default {
price: 'Free', price: 'Free',
tags: 'Brand', tags: 'Brand',
category: 'Brand', category: 'Brand',
input_schema: JSON.stringify(input), input_schema: JSON.stringify(input)
}; };

69
packages/nocodb/src/lib/noco/plugins/cache.ts

@ -1,39 +1,43 @@
import {XcActionType, XcForm, XcType} from "nc-common"; import { XcActionType, XcForm, XcType } from 'nc-common';
const input: XcForm = { const input: XcForm = {
title: 'Configure Metadata LRU Cache', title: 'Configure Metadata LRU Cache',
items: [{ items: [
key: 'max', {
label: 'Maximum Size', key: 'max',
placeholder: 'Maximum Size', label: 'Maximum Size',
type: XcType.SingleLineText, placeholder: 'Maximum Size',
required: true type: XcType.SingleLineText,
}, { required: true
key: 'maxAge', },
label: 'Maximum Age(in ms)', {
placeholder: 'Maximum Age(in ms)', key: 'maxAge',
type: XcType.SingleLineText, label: 'Maximum Age(in ms)',
required: true placeholder: 'Maximum Age(in ms)',
},], type: XcType.SingleLineText,
actions: [{ required: true
label: 'Test', }
placeholder: 'Test', ],
key: 'test', actions: [
actionType: XcActionType.TEST, {
type: XcType.Button label: 'Test',
}, { placeholder: 'Test',
label: 'Save', key: 'test',
placeholder: 'Save', actionType: XcActionType.TEST,
key: 'save', type: XcType.Button
actionType: XcActionType.SUBMIT, },
type: XcType.Button {
},], label: 'Save',
placeholder: 'Save',
key: 'save',
actionType: XcActionType.SUBMIT,
type: XcType.Button
}
],
msgOnInstall: 'Successfully updated LRU cache options.', msgOnInstall: 'Successfully updated LRU cache options.',
msgOnUninstall: '', msgOnUninstall: ''
}; };
export default { export default {
title: 'Metadata LRU Cache', title: 'Metadata LRU Cache',
version: '0.0.1', version: '0.0.1',
@ -44,7 +48,8 @@ export default {
category: 'Cache', category: 'Cache',
active: true, active: true,
input: JSON.stringify({ input: JSON.stringify({
max: 500, maxAge: 1000 * 60 * 60 * 24 max: 500,
maxAge: 1000 * 60 * 60 * 24
}), }),
input_schema: JSON.stringify(input) input_schema: JSON.stringify(input)
}; };

72
packages/nocodb/src/lib/noco/plugins/discord.ts

@ -1,47 +1,53 @@
import {XcActionType, XcForm, XcType} from "nc-common"; import { XcActionType, XcForm, XcType } from 'nc-common';
const input: XcForm = { const input: XcForm = {
title: 'Configure Discord', title: 'Configure Discord',
array: true, array: true,
items: [{ items: [
key: 'channel', {
label: 'Channel Name', key: 'channel',
placeholder: 'Channel Name', label: 'Channel Name',
type: XcType.SingleLineText, placeholder: 'Channel Name',
required: true type: XcType.SingleLineText,
}, { required: true
key: 'webhook_url', },
label: 'Webhook URL', {
type: XcType.Password, key: 'webhook_url',
placeholder: 'Webhook URL', label: 'Webhook URL',
required: true type: XcType.Password,
}], placeholder: 'Webhook URL',
actions: [{ required: true
label: 'Test', }
placeholder: 'Test', ],
key: 'test', actions: [
actionType: XcActionType.TEST, {
type: XcType.Button label: 'Test',
}, { placeholder: 'Test',
label: 'Save', key: 'test',
placeholder: 'Save', actionType: XcActionType.TEST,
key: 'save', type: XcType.Button
actionType: XcActionType.SUBMIT, },
type: XcType.Button {
},], label: 'Save',
msgOnInstall:'Successfully installed and Discord is enabled for notification.', placeholder: 'Save',
msgOnUninstall:'', key: 'save',
actionType: XcActionType.SUBMIT,
type: XcType.Button
}
],
msgOnInstall:
'Successfully installed and Discord is enabled for notification.',
msgOnUninstall: ''
}; };
export default { export default {
title: 'Discord', title: 'Discord',
version: '0.0.1', version: '0.0.1',
logo: 'plugins/discord.png', logo: 'plugins/discord.png',
description: 'Discord is the easiest way to talk over voice, video, and text. Talk, chat, hang out, and stay close with your friends and communities.', description:
'Discord is the easiest way to talk over voice, video, and text. Talk, chat, hang out, and stay close with your friends and communities.',
price: 'Free', price: 'Free',
tags: 'Chat', tags: 'Chat',
category: 'Chat', category: 'Chat',
input_schema: JSON.stringify(input) input_schema: JSON.stringify(input)
}; };

54
packages/nocodb/src/lib/noco/plugins/ee.ts

@ -1,41 +1,43 @@
import {XcActionType, XcForm, XcType} from "nc-common"; import { XcActionType, XcForm, XcType } from 'nc-common';
const input: XcForm = { const input: XcForm = {
title: 'Configure Enterprise Edition', title: 'Configure Enterprise Edition',
items: [{ items: [
key: 'key', {
label: 'Key', key: 'key',
placeholder: 'Key', label: 'Key',
type: XcType.Password, placeholder: 'Key',
required: true type: XcType.Password,
}, required: true
}
// { // {
// key: 'callback_url', // key: 'callback_url',
// label: 'Callback URL', // label: 'Callback URL',
// placeholder: 'Callback URL', // placeholder: 'Callback URL',
// type: XcType.URL, // type: XcType.URL,
// required: true // required: true
// }, // },
],
actions: [
{
label: 'Test',
placeholder: 'Test',
key: 'test',
actionType: XcActionType.TEST,
type: XcType.Button
},
{
label: 'Save',
placeholder: 'Save',
key: 'save',
actionType: XcActionType.SUBMIT,
type: XcType.Button
}
], ],
actions: [{
label: 'Test',
placeholder: 'Test',
key: 'test',
actionType: XcActionType.TEST,
type: XcType.Button
}, {
label: 'Save',
placeholder: 'Save',
key: 'save',
actionType: XcActionType.SUBMIT,
type: XcType.Button
},],
msgOnInstall: 'Successfully installed and enabled Enterprise Edition.', msgOnInstall: 'Successfully installed and enabled Enterprise Edition.',
msgOnUninstall: '', msgOnUninstall: ''
}; };
export default { export default {
title: 'Enterprise Edition', title: 'Enterprise Edition',
version: '0.0.1', version: '0.0.1',
@ -45,4 +47,4 @@ export default {
tags: 'Enterprise', tags: 'Enterprise',
category: 'Enterprise', category: 'Enterprise',
input_schema: JSON.stringify(input) input_schema: JSON.stringify(input)
}; };

81
packages/nocodb/src/lib/noco/plugins/githubAuth.ts

@ -1,44 +1,51 @@
import {XcActionType, XcForm, XcType} from "nc-common"; import { XcActionType, XcForm, XcType } from 'nc-common';
const input: XcForm = { const input: XcForm = {
title: 'Configure Github Auth', title: 'Configure Github Auth',
items: [{ items: [
key: 'client_id', {
label: 'Client ID', key: 'client_id',
placeholder: 'Client ID', label: 'Client ID',
type: XcType.SingleLineText, placeholder: 'Client ID',
required: true type: XcType.SingleLineText,
},{ required: true
key: 'client_secret', },
label: 'Client Secret', {
placeholder: 'Client Secret', key: 'client_secret',
type: XcType.Password, label: 'Client Secret',
required: true placeholder: 'Client Secret',
},{ type: XcType.Password,
key: 'redirect_url', required: true
label: 'Redirect URL', },
placeholder: 'Redirect URL', {
type: XcType.SingleLineText, key: 'redirect_url',
required: true label: 'Redirect URL',
}, ], placeholder: 'Redirect URL',
actions: [{ type: XcType.SingleLineText,
label: 'Test', required: true
placeholder: 'Test', }
key: 'test', ],
actionType: XcActionType.TEST, actions: [
type: XcType.Button {
}, { label: 'Test',
label: 'Save', placeholder: 'Test',
placeholder: 'Save', key: 'test',
key: 'save', actionType: XcActionType.TEST,
actionType: XcActionType.SUBMIT, type: XcType.Button
type: XcType.Button },
},], {
msgOnInstall:'Successfully installed and configured Github Authentication, restart NocoDB', label: 'Save',
msgOnUninstall:'', placeholder: 'Save',
key: 'save',
actionType: XcActionType.SUBMIT,
type: XcType.Button
}
],
msgOnInstall:
'Successfully installed and configured Github Authentication, restart NocoDB',
msgOnUninstall: ''
}; };
export default { export default {
title: 'Github', title: 'Github',
version: '0.0.1', version: '0.0.1',
@ -48,4 +55,4 @@ export default {
tags: 'Authentication', tags: 'Authentication',
category: 'Github', category: 'Github',
input_schema: JSON.stringify(input) input_schema: JSON.stringify(input)
}; };

81
packages/nocodb/src/lib/noco/plugins/googleAuth.ts

@ -1,44 +1,51 @@
import {XcActionType, XcForm, XcType} from "nc-common"; import { XcActionType, XcForm, XcType } from 'nc-common';
const input: XcForm = { const input: XcForm = {
title: 'Configure Google Auth', title: 'Configure Google Auth',
items: [{ items: [
key: 'client_id', {
label: 'Client ID', key: 'client_id',
placeholder: 'Client ID', label: 'Client ID',
type: XcType.SingleLineText, placeholder: 'Client ID',
required: true type: XcType.SingleLineText,
}, { required: true
key: 'client_secret', },
label: 'Client Secret', {
placeholder: 'Client Secret', key: 'client_secret',
type: XcType.Password, label: 'Client Secret',
required: true placeholder: 'Client Secret',
}, { type: XcType.Password,
key: 'redirect_url', required: true
label: 'Redirect URL', },
placeholder: 'Redirect URL', {
type: XcType.SingleLineText, key: 'redirect_url',
required: true label: 'Redirect URL',
},], placeholder: 'Redirect URL',
actions: [{ type: XcType.SingleLineText,
label: 'Test', required: true
placeholder: 'Test', }
key: 'test', ],
actionType: XcActionType.TEST, actions: [
type: XcType.Button {
}, { label: 'Test',
label: 'Save', placeholder: 'Test',
placeholder: 'Save', key: 'test',
key: 'save', actionType: XcActionType.TEST,
actionType: XcActionType.SUBMIT, type: XcType.Button
type: XcType.Button },
},], {
msgOnInstall: 'Successfully installed and configured Google Authentication, restart NocoDB', label: 'Save',
msgOnUninstall: '', placeholder: 'Save',
key: 'save',
actionType: XcActionType.SUBMIT,
type: XcType.Button
}
],
msgOnInstall:
'Successfully installed and configured Google Authentication, restart NocoDB',
msgOnUninstall: ''
}; };
export default { export default {
title: 'Google', title: 'Google',
version: '0.0.1', version: '0.0.1',
@ -48,4 +55,4 @@ export default {
tags: 'Authentication', tags: 'Authentication',
category: 'Google', category: 'Google',
input_schema: JSON.stringify(input) input_schema: JSON.stringify(input)
}; };

72
packages/nocodb/src/lib/noco/plugins/mattermost.ts

@ -1,47 +1,53 @@
import {XcActionType, XcForm, XcType} from "nc-common"; import { XcActionType, XcForm, XcType } from 'nc-common';
const input: XcForm = { const input: XcForm = {
title: 'Configure Mattermost', title: 'Configure Mattermost',
array: true, array: true,
items: [{ items: [
key: 'channel', {
label: 'Channel Name', key: 'channel',
placeholder: 'Channel Name', label: 'Channel Name',
type: XcType.SingleLineText, placeholder: 'Channel Name',
required: true type: XcType.SingleLineText,
}, { required: true
key: 'webhook_url', },
label: 'Webhook URL', {
placeholder: 'Webhook URL', key: 'webhook_url',
type: XcType.Password, label: 'Webhook URL',
required: true placeholder: 'Webhook URL',
},], type: XcType.Password,
actions: [{ required: true
label: 'Test', }
placeholder: 'Test', ],
key: 'test', actions: [
actionType: XcActionType.TEST, {
type: XcType.Button label: 'Test',
}, { placeholder: 'Test',
label: 'Save', key: 'test',
placeholder: 'Save', actionType: XcActionType.TEST,
key: 'save', type: XcType.Button
actionType: XcActionType.SUBMIT, },
type: XcType.Button {
},], label: 'Save',
msgOnInstall: 'Successfully installed and Mattermost is enabled for notification.', placeholder: 'Save',
msgOnUninstall: '', key: 'save',
actionType: XcActionType.SUBMIT,
type: XcType.Button
}
],
msgOnInstall:
'Successfully installed and Mattermost is enabled for notification.',
msgOnUninstall: ''
}; };
export default { export default {
title: 'Mattermost', title: 'Mattermost',
version: '0.0.1', version: '0.0.1',
logo: 'plugins/mattermost.png', logo: 'plugins/mattermost.png',
description: 'Mattermost brings all your team communication into one place, making it searchable and accessible anywhere.', description:
'Mattermost brings all your team communication into one place, making it searchable and accessible anywhere.',
price: 'Free', price: 'Free',
tags: 'Chat', tags: 'Chat',
category: 'Chat', category: 'Chat',
input_schema: JSON.stringify(input) input_schema: JSON.stringify(input)
}; };

123
packages/nocodb/src/lib/noco/plugins/ses.ts

@ -1,69 +1,80 @@
import {XcActionType, XcForm, XcType} from "nc-common"; import { XcActionType, XcForm, XcType } from 'nc-common';
const input: XcForm = { const input: XcForm = {
title: 'Configure Amazon Simple Email Service(SES)', title: 'Configure Amazon Simple Email Service(SES)',
items: [{ items: [
key: 'from', {
label: 'From', key: 'from',
placeholder: 'From', label: 'From',
type: XcType.SingleLineText, placeholder: 'From',
required: true type: XcType.SingleLineText,
}, { required: true
key: 'host', },
label: 'Jost', {
placeholder: 'Jost', key: 'host',
type: XcType.SingleLineText, label: 'Jost',
required: true placeholder: 'Jost',
}, { type: XcType.SingleLineText,
key: 'port', required: true
label: 'Port', },
placeholder: 'Port', {
type: XcType.SingleLineText, key: 'port',
required: true label: 'Port',
}, { placeholder: 'Port',
key: 'secure', type: XcType.SingleLineText,
label: 'Secure', required: true
placeholder: 'Secure', },
type: XcType.SingleLineText, {
required: true key: 'secure',
}, { label: 'Secure',
key: 'username', placeholder: 'Secure',
label: 'Username', type: XcType.SingleLineText,
placeholder: 'Username', required: true
type: XcType.SingleLineText, },
required: true {
},{ key: 'username',
key: 'password', label: 'Username',
label: 'Password', placeholder: 'Username',
placeholder: 'Password', type: XcType.SingleLineText,
type: XcType.Password, required: true
required: true },
},], {
actions: [{ key: 'password',
label: 'Test', label: 'Password',
placeholder: 'Test', placeholder: 'Password',
key: 'test', type: XcType.Password,
actionType: XcActionType.TEST, required: true
type: XcType.Button }
}, { ],
label: 'Save', actions: [
placeholder: 'Save', {
key: 'save', label: 'Test',
actionType: XcActionType.SUBMIT, placeholder: 'Test',
type: XcType.Button key: 'test',
},], actionType: XcActionType.TEST,
msgOnInstall:'Successfully installed and email notification will use Amazon SES', type: XcType.Button
msgOnUninstall:'', },
{
label: 'Save',
placeholder: 'Save',
key: 'save',
actionType: XcActionType.SUBMIT,
type: XcType.Button
}
],
msgOnInstall:
'Successfully installed and email notification will use Amazon SES',
msgOnUninstall: ''
}; };
export default { export default {
title: 'SES', title: 'SES',
version: '0.0.1', version: '0.0.1',
logo: 'plugins/aws.png', logo: 'plugins/aws.png',
description: 'Amazon Simple Email Service (SES) is a cost-effective, flexible, and scalable email service that enables developers to send mail from within any application.', description:
'Amazon Simple Email Service (SES) is a cost-effective, flexible, and scalable email service that enables developers to send mail from within any application.',
price: 'Free', price: 'Free',
tags: 'Email', tags: 'Email',
category: 'Email', category: 'Email',
input_schema: JSON.stringify(input) input_schema: JSON.stringify(input)
}; };

69
packages/nocodb/src/lib/noco/plugins/slack.ts

@ -1,47 +1,52 @@
import {XcActionType, XcForm, XcType} from "nc-common"; import { XcActionType, XcForm, XcType } from 'nc-common';
const input: XcForm = { const input: XcForm = {
title: 'Configure Slack', title: 'Configure Slack',
array: true, array: true,
items: [{ items: [
key: 'channel', {
label: 'Channel Name', key: 'channel',
placeholder: 'Channel Name', label: 'Channel Name',
type: XcType.SingleLineText, placeholder: 'Channel Name',
required: true type: XcType.SingleLineText,
}, { required: true
key: 'webhook_url', },
label: 'Webhook URL', {
placeholder: 'Webhook URL', key: 'webhook_url',
type: XcType.Password, label: 'Webhook URL',
required: true placeholder: 'Webhook URL',
},], type: XcType.Password,
actions: [{ required: true
label: 'Test', }
placeholder: 'Test', ],
key: 'test', actions: [
actionType: XcActionType.TEST, {
type: XcType.Button label: 'Test',
}, { placeholder: 'Test',
label: 'Save', key: 'test',
placeholder: 'Save', actionType: XcActionType.TEST,
key: 'save', type: XcType.Button
actionType: XcActionType.SUBMIT, },
type: XcType.Button {
},], label: 'Save',
placeholder: 'Save',
key: 'save',
actionType: XcActionType.SUBMIT,
type: XcType.Button
}
],
msgOnInstall: 'Successfully installed and Slack is enabled for notification.', msgOnInstall: 'Successfully installed and Slack is enabled for notification.',
msgOnUninstall: '', msgOnUninstall: ''
}; };
export default { export default {
title: 'Slack', title: 'Slack',
version: '0.0.1', version: '0.0.1',
logo: 'plugins/slack.webp', logo: 'plugins/slack.webp',
description: 'Slack brings team communication and collaboration into one place so you can get more work done, whether you belong to a large enterprise or a small business. ', description:
'Slack brings team communication and collaboration into one place so you can get more work done, whether you belong to a large enterprise or a small business. ',
price: 'Free', price: 'Free',
tags: 'Chat', tags: 'Chat',
category: 'Chat', category: 'Chat',
input_schema: JSON.stringify(input) input_schema: JSON.stringify(input)
}; };

129
packages/nocodb/src/lib/noco/plugins/smtp.ts

@ -1,66 +1,77 @@
import {XcActionType, XcForm, XcType} from "nc-common"; import { XcActionType, XcForm, XcType } from 'nc-common';
const input: XcForm = { const input: XcForm = {
title: 'Configure Email SMTP', title: 'Configure Email SMTP',
items: [{ items: [
key: 'from', {
label: 'From', key: 'from',
placeholder: 'eg: admin@example.com', label: 'From',
type: XcType.SingleLineText, placeholder: 'eg: admin@example.com',
required: true type: XcType.SingleLineText,
}, { required: true
key: 'host', },
label: 'Host', {
placeholder: 'eg: smtp.example.com', key: 'host',
type: XcType.SingleLineText, label: 'Host',
required: true placeholder: 'eg: smtp.example.com',
}, { type: XcType.SingleLineText,
key: 'port', required: true
label: 'Port', },
placeholder: 'Port', {
type: XcType.SingleLineText, key: 'port',
required: true label: 'Port',
}, { placeholder: 'Port',
key: 'secure', type: XcType.SingleLineText,
label: 'Secure', required: true
placeholder: 'Secure', },
type: XcType.SingleLineText, {
required: true key: 'secure',
}, { label: 'Secure',
key: 'ignoreTLS', placeholder: 'Secure',
label: 'IgnoreTLS', type: XcType.SingleLineText,
placeholder: 'IgnoreTLS', required: true
type: XcType.SingleLineText, },
required: true {
}, { key: 'ignoreTLS',
key: 'username', label: 'IgnoreTLS',
label: 'Username', placeholder: 'IgnoreTLS',
placeholder: 'Username', type: XcType.SingleLineText,
type: XcType.SingleLineText, required: true
required: true },
}, { {
key: 'password', key: 'username',
label: 'Password', label: 'Username',
placeholder: 'Password', placeholder: 'Username',
type: XcType.Password, type: XcType.SingleLineText,
required: true required: true
},], },
actions: [{ {
label: 'Test', key: 'password',
key: 'test', label: 'Password',
actionType: XcActionType.TEST, placeholder: 'Password',
type: XcType.Button type: XcType.Password,
}, { required: true
label: 'Save', }
key: 'save', ],
actionType: XcActionType.SUBMIT, actions: [
type: XcType.Button {
},], label: 'Test',
msgOnInstall: 'Successfully installed and email notification will use SMTP configuration', key: 'test',
msgOnUninstall: '', actionType: XcActionType.TEST,
type: XcType.Button
},
{
label: 'Save',
key: 'save',
actionType: XcActionType.SUBMIT,
type: XcType.Button
}
],
msgOnInstall:
'Successfully installed and email notification will use SMTP configuration',
msgOnUninstall: ''
}; };
export default { export default {
title: 'SMTP', title: 'SMTP',
version: '0.0.1', version: '0.0.1',
@ -70,4 +81,4 @@ export default {
tags: 'Email', tags: 'Email',
category: 'Email', category: 'Email',
input_schema: JSON.stringify(input) input_schema: JSON.stringify(input)
}; };

85
packages/nocodb/src/lib/noco/plugins/twilio.ts

@ -1,52 +1,59 @@
import {XcActionType, XcForm, XcType} from "nc-common"; import { XcActionType, XcForm, XcType } from 'nc-common';
const input: XcForm = { const input: XcForm = {
title: 'Configure Twilio', title: 'Configure Twilio',
items: [{ items: [
key: 'sid', {
label: 'Account SID', key: 'sid',
placeholder: 'Account SID', label: 'Account SID',
type: XcType.SingleLineText, placeholder: 'Account SID',
required: true type: XcType.SingleLineText,
}, { required: true
key: 'token', },
label: 'Auth Token' , {
placeholder: 'Auth Token', key: 'token',
type: XcType.Password, label: 'Auth Token',
required: true placeholder: 'Auth Token',
}, { type: XcType.Password,
key: 'from', required: true
label: 'From Phone Number', },
placeholder: 'From Phone Number', {
type: XcType.SingleLineText, key: 'from',
required: true label: 'From Phone Number',
},], placeholder: 'From Phone Number',
actions: [{ type: XcType.SingleLineText,
label: 'Test', required: true
placeholder: 'Test', }
key: 'test', ],
actionType: XcActionType.TEST, actions: [
type: XcType.Button {
}, { label: 'Test',
label: 'Save', placeholder: 'Test',
placeholder: 'Save', key: 'test',
key: 'save', actionType: XcActionType.TEST,
actionType: XcActionType.SUBMIT, type: XcType.Button
type: XcType.Button },
},], {
msgOnInstall: 'Successfully installed and Twilio is enabled for notification.', label: 'Save',
msgOnUninstall: '', placeholder: 'Save',
key: 'save',
actionType: XcActionType.SUBMIT,
type: XcType.Button
}
],
msgOnInstall:
'Successfully installed and Twilio is enabled for notification.',
msgOnUninstall: ''
}; };
export default { export default {
title: 'Twilio', title: 'Twilio',
version: '0.0.1', version: '0.0.1',
logo: 'plugins/twilio.png', logo: 'plugins/twilio.png',
description: 'With Twilio, unite communications and strengthen customer relationships across your business – from marketing and sales to customer service and operations.', description:
'With Twilio, unite communications and strengthen customer relationships across your business – from marketing and sales to customer service and operations.',
price: 'Free', price: 'Free',
tags: 'Chat', tags: 'Chat',
category: 'Twilio', category: 'Twilio',
input_schema: JSON.stringify(input) input_schema: JSON.stringify(input)
}; };

2248
packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts

File diff suppressed because it is too large Load Diff

1390
packages/nocodb/src/lib/noco/rest/RestAuthCtrl.ts

File diff suppressed because it is too large Load Diff

233
packages/nocodb/src/lib/noco/rest/RestAuthCtrlEE.ts

@ -1,32 +1,33 @@
import {Tele} from 'nc-help'; import { Tele } from 'nc-help';
import passport from 'passport'; import passport from 'passport';
import {Strategy} from 'passport-jwt'; import { Strategy } from 'passport-jwt';
import {v4 as uuidv4} from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import validator from "validator"; import validator from 'validator';
import XcCache from "../plugins/adapters/cache/XcCache"; import XcCache from '../plugins/adapters/cache/XcCache';
import RestAuthCtrl from "./RestAuthCtrl"; import RestAuthCtrl from './RestAuthCtrl';
export default class RestAuthCtrlEE extends RestAuthCtrl { export default class RestAuthCtrlEE extends RestAuthCtrl {
protected async addAdmin(req, res, next): Promise<any> { protected async addAdmin(req, res, next): Promise<any> {
const emails = (req.body.email || '').split(/\s*,\s*/).map(v => v.trim()); const emails = (req.body.email || '').split(/\s*,\s*/).map(v => v.trim());
// check for invalid emails // check for invalid emails
const invalidEmails = emails.filter(v => !validator.isEmail(v)) const invalidEmails = emails.filter(v => !validator.isEmail(v));
if (!emails.length) { if (!emails.length) {
return next(new Error('Invalid email address')); return next(new Error('Invalid email address'));
} }
if (invalidEmails.length) { if (invalidEmails.length) {
return next(new Error('Invalid email address : ' + invalidEmails.join(', '))); return next(
new Error('Invalid email address : ' + invalidEmails.join(', '))
);
} }
// todo: handle roles which contains super // todo: handle roles which contains super
if (!req.session?.passport?.user?.roles?.owner && req.body.roles.indexOf('owner') > -1) { if (
!req.session?.passport?.user?.roles?.owner &&
req.body.roles.indexOf('owner') > -1
) {
return next(new Error('Insufficient privilege to add super admin role.')); return next(new Error('Insufficient privilege to add super admin role.'));
} }
@ -34,95 +35,122 @@ export default class RestAuthCtrlEE extends RestAuthCtrl {
const error = []; const error = [];
for (const email of emails) { for (const email of emails) {
// add user to project if user already exist // add user to project if user already exist
const user = await this.users.where({email}).first(); const user = await this.users.where({ email }).first();
if (user) { if (user) {
await this.users
await this.users.update({ .update({
roles: 'user' roles: 'user'
}).where({roles: 'user_new', email}); })
.where({ roles: 'user_new', email });
if (!await this.xcMeta.isUserHaveAccessToProject(req.body.project_id, user.id)) {
await this.xcMeta.projectAddUser(req.body.project_id, user.id, 'editor'); if (
!(await this.xcMeta.isUserHaveAccessToProject(
req.body.project_id,
user.id
))
) {
await this.xcMeta.projectAddUser(
req.body.project_id,
user.id,
'editor'
);
} }
this.xcMeta.audit(req.body.project_id, null, 'nc_audit', { this.xcMeta.audit(req.body.project_id, null, 'nc_audit', {
op_type: 'AUTHENTICATION', op_type: 'AUTHENTICATION',
op_sub_type: 'INVITE', op_sub_type: 'INVITE',
user: req.user.email, user: req.user.email,
description: `invited ${email} to ${req.body.project_id} project `, ip: req.clientIp description: `invited ${email} to ${req.body.project_id} project `,
}) ip: req.clientIp
});
} else { } else {
try { try {
// create new user with invite token // create new user with invite token
await this.users.insert({ await this.users.insert({
invite_token, invite_token,
invite_token_expires: new Date(Date.now() + (24 * 60 * 60 * 1000)), invite_token_expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
email, email,
roles: 'user' roles: 'user'
}); });
const {id} = await this.users.where({email}).first(); const { id } = await this.users.where({ email }).first();
const count = await this.users.count('id').first(); const count = await this.users.count('id').first();
// add user to project // add user to project
await this.xcMeta.projectAddUser(req.body.project_id, id, req.body.roles); await this.xcMeta.projectAddUser(
req.body.project_id,
id,
req.body.roles
);
Tele.emit('evt', {evt_type: 'project:invite', count: count?.count}); Tele.emit('evt', { evt_type: 'project:invite', count: count?.count });
this.xcMeta.audit(req.body.project_id, null, 'nc_audit', { this.xcMeta.audit(req.body.project_id, null, 'nc_audit', {
op_type: 'AUTHENTICATION', op_type: 'AUTHENTICATION',
op_sub_type: 'INVITE', op_sub_type: 'INVITE',
user: req.user.email, user: req.user.email,
description: `invited ${email} to ${req.body.project_id} project `, ip: req.clientIp description: `invited ${email} to ${req.body.project_id} project `,
}) ip: req.clientIp
});
// in case of single user check for smtp failure // in case of single user check for smtp failure
// and send back token if failed // and send back token if failed
if (emails.length === 1 && !await this.sendInviteEmail(email, invite_token, req)) { if (
return res.json({invite_token, email}); emails.length === 1 &&
!(await this.sendInviteEmail(email, invite_token, req))
) {
return res.json({ invite_token, email });
} else { } else {
this.sendInviteEmail(email, invite_token, req) this.sendInviteEmail(email, invite_token, req);
} }
} catch (e) { } catch (e) {
if (emails.length === 1) { if (emails.length === 1) {
return next(e); return next(e);
} else { } else {
error.push({email, error: e.message}) error.push({ email, error: e.message });
} }
} }
} }
} }
if (emails.length === 1) { if (emails.length === 1) {
res.json({ res.json({
msg: 'success' msg: 'success'
}) });
} else { } else {
return res.json({invite_token, emails, error}); return res.json({ invite_token, emails, error });
} }
} }
protected async updateAdmin(req, res, next): Promise<any> { protected async updateAdmin(req, res, next): Promise<any> {
if (!req?.body?.project_id) { if (!req?.body?.project_id) {
return next(new Error('Missing project id in request body.')); return next(new Error('Missing project id in request body.'));
} }
if (req.session?.passport?.user?.roles?.owner && req.session?.passport?.user?.id === +req.params.id && req.body.roles.indexOf('owner') === -1) { if (
return next(new Error('Super admin can\'t remove Super role themselves')); req.session?.passport?.user?.roles?.owner &&
req.session?.passport?.user?.id === +req.params.id &&
req.body.roles.indexOf('owner') === -1
) {
return next(new Error("Super admin can't remove Super role themselves"));
} }
try { try {
const user = await this.users.where({ const user = await this.users
id: req.params.id .where({
}).first(); id: req.params.id
})
.first();
if (!user) { if (!user) {
return next(`User with id '${req.params.id}' doesn't exist`); return next(`User with id '${req.params.id}' doesn't exist`);
} }
// todo: handle roles which contains super // todo: handle roles which contains super
if (!req.session?.passport?.user?.roles?.owner && req.body.roles.indexOf('owner') > -1) { if (
return next(new Error('Insufficient privilege to add super admin role.')); !req.session?.passport?.user?.roles?.owner &&
req.body.roles.indexOf('owner') > -1
) {
return next(
new Error('Insufficient privilege to add super admin role.')
);
} }
// await this.users.update({ // await this.users.update({
@ -131,77 +159,88 @@ export default class RestAuthCtrlEE extends RestAuthCtrl {
// id: req.params.id // id: req.params.id
// }); // });
await this.xcMeta.metaUpdate(
await this.xcMeta.metaUpdate(req?.body?.project_id, null, 'nc_projects_users', { req?.body?.project_id,
roles: req.body.roles null,
}, { 'nc_projects_users',
user_id: req.params.id, {
// email: req.body.email roles: req.body.roles
}); },
{
user_id: req.params.id
// email: req.body.email
}
);
XcCache.del(`${req.body.email}___${req?.body?.project_id}`); XcCache.del(`${req.body.email}___${req?.body?.project_id}`);
this.xcMeta.audit(null, null, 'nc_audit', { this.xcMeta.audit(null, null, 'nc_audit', {
op_type: 'AUTHENTICATION', op_type: 'AUTHENTICATION',
op_sub_type: 'ROLES_MANAGEMENT', op_sub_type: 'ROLES_MANAGEMENT',
user: req.user.email, user: req.user.email,
description: `updated roles for ${user.email} with ${req.body.roles} `, ip: req.clientIp description: `updated roles for ${user.email} with ${req.body.roles} `,
}) ip: req.clientIp
});
res.json({ res.json({
msg: 'User details updated successfully' msg: 'User details updated successfully'
}) });
} catch (e) { } catch (e) {
next(e); next(e);
} }
} }
protected initJwtStrategy(): void { protected initJwtStrategy(): void {
passport.use(new Strategy({ passport.use(
...this.jwtOptions, new Strategy(
passReqToCallback: true {
}, (req, jwtPayload, done) => { ...this.jwtOptions,
passReqToCallback: true
const keyVals = [jwtPayload?.email] },
if (req.ncProjectId) { (req, jwtPayload, done) => {
keyVals.push(req.ncProjectId); const keyVals = [jwtPayload?.email];
} if (req.ncProjectId) {
const key = keyVals.join('___'); keyVals.push(req.ncProjectId);
const cachedVal = XcCache.get(key); }
if (cachedVal) { const key = keyVals.join('___');
return done(null, cachedVal); const cachedVal = XcCache.get(key);
} if (cachedVal) {
return done(null, cachedVal);
this.users.where({
email: jwtPayload?.email
}).first().then(user => {
if (req.ncProjectId) {
this.xcMeta.metaGet(req.ncProjectId, null, 'nc_projects_users', {
user_id: user?.id
}).then(projectUser => {
user.roles = projectUser.roles;
XcCache.set(key, user);
done(null, user)
})
} else {
// const roles = projectUser?.roles ? JSON.parse(projectUser.roles) : {guest: true};
if (user) {
XcCache.set(key, user);
return done(null, user);
} else {
return done(new Error('User not found'));
} }
this.users
.where({
email: jwtPayload?.email
})
.first()
.then(user => {
if (req.ncProjectId) {
this.xcMeta
.metaGet(req.ncProjectId, null, 'nc_projects_users', {
user_id: user?.id
})
.then(projectUser => {
user.roles = projectUser.roles;
XcCache.set(key, user);
done(null, user);
});
} else {
// const roles = projectUser?.roles ? JSON.parse(projectUser.roles) : {guest: true};
if (user) {
XcCache.set(key, user);
return done(null, user);
} else {
return done(new Error('User not found'));
}
}
})
.catch(err => {
return done(err);
});
} }
}).catch(err => { )
return done(err); );
})
}));
} }
} }
/** /**

89
packages/nocodb/src/lib/noco/rest/RestBaseCtrl.ts

@ -1,17 +1,15 @@
import {Handler, NextFunction, Request, Response, Router} from "express"; import { Handler, NextFunction, Request, Response, Router } from 'express';
import Handlebars from "handlebars"; import Handlebars from 'handlebars';
import {Route} from "../../../interface/config"; import { Route } from '../../../interface/config';
export abstract class RestBaseCtrl { export abstract class RestBaseCtrl {
public router: Router; public router: Router;
public routes: Route[]; public routes: Route[];
protected rootPath: string; protected rootPath: string;
protected middlewareBody: string; protected middlewareBody: string;
public updateMiddleware(middlewareBody: string): this { public updateMiddleware(middlewareBody: string): this {
this.middlewareBody = middlewareBody; this.middlewareBody = middlewareBody;
return this; return this;
@ -22,9 +20,10 @@ export abstract class RestBaseCtrl {
return this; return this;
} }
public mapRoutes(router: Router, customRoutes?: any): any { public mapRoutes(router: Router, customRoutes?: any): any {
const middleware = this.middlewareBody ? this.generateHandlerFromStringBody(this.middlewareBody) : this.middleware; const middleware = this.middlewareBody
? this.generateHandlerFromStringBody(this.middlewareBody)
: this.middleware;
customRoutes?.additional?.[this.controllerName]?.forEach(addRoute => { customRoutes?.additional?.[this.controllerName]?.forEach(addRoute => {
const handlers = [middleware]; const handlers = [middleware];
@ -33,31 +32,50 @@ export abstract class RestBaseCtrl {
handlers.push(this.postMiddleware); handlers.push(this.postMiddleware);
router[addRoute.method](encodeURI(addRoute.path.slice(this.rootPath.length)), ...handlers); router[addRoute.method](
encodeURI(addRoute.path.slice(this.rootPath.length)),
}) ...handlers
);
});
this.routes.forEach((route: Route) => { this.routes.forEach((route: Route) => {
const handlers = [middleware]; const handlers = [middleware];
if (customRoutes?.override?.[route.path]?.[route.type]?.length) { if (customRoutes?.override?.[route.path]?.[route.type]?.length) {
handlers.push( handlers.push(
...customRoutes.override[route.path][route.type].map(hn => this.catchErr(hn)) ...customRoutes.override[route.path][route.type].map(hn =>
this.catchErr(hn)
)
);
} else if (
route.functions &&
Array.isArray(route.functions) &&
route.functions.length
) {
handlers.push(
...route.functions.map(fnBody => {
return this.catchErr(this.generateHandlerFromStringBody(fnBody));
})
); );
} else if (route.functions && Array.isArray(route.functions) && route.functions.length) {
handlers.push(...route.functions.map(fnBody => {
return this.catchErr(this.generateHandlerFromStringBody(fnBody))
}));
} else { } else {
handlers.push(...route.handler.map(h => { handlers.push(
return this.catchErr((typeof h === 'string' ? (h in this ? this[h] : ((_req, res) => res.json({}))).bind(this) : h)); ...route.handler.map(h => {
})) return this.catchErr(
typeof h === 'string'
? (h in this ? this[h] : (_req, res) => res.json({})).bind(this)
: h
);
})
);
} }
handlers.push(this.postMiddleware); handlers.push(this.postMiddleware);
router[route.type](encodeURI(route.path.slice(this.rootPath.length)), ...handlers); router[route.type](
}) encodeURI(route.path.slice(this.rootPath.length)),
...handlers
);
});
} }
protected generateHandlerFromStringBody(fnBody: string): Handler { protected generateHandlerFromStringBody(fnBody: string): Handler {
@ -72,12 +90,11 @@ export abstract class RestBaseCtrl {
// tslint:disable-next-line:no-eval // tslint:disable-next-line:no-eval
handler = eval(js); handler = eval(js);
} catch (e) { } catch (e) {
console.log('Error in transpilation', e) console.log('Error in transpilation', e);
} }
return handler; return handler;
} }
protected generateMiddlewareFromStringBody(fnBody: string): Handler { protected generateMiddlewareFromStringBody(fnBody: string): Handler {
// @ts-ignore // @ts-ignore
let middleware = (_req: Request, res: Response, _next: NextFunction) => { let middleware = (_req: Request, res: Response, _next: NextFunction) => {
@ -88,37 +105,45 @@ export abstract class RestBaseCtrl {
const js = `((${fnBody}).bind(this))`; const js = `((${fnBody}).bind(this))`;
// tslint:disable-next-line:no-eval // tslint:disable-next-line:no-eval
middleware = eval(js); middleware = eval(js);
} catch (e) { } catch (e) {
console.log('Error in transpilation', e) console.log('Error in transpilation', e);
} }
return middleware; return middleware;
} }
protected catchErr(handler): Handler { protected catchErr(handler): Handler {
return (req, res, next) => { return (req, res, next) => {
(res as any).xcJson = (data) => { (res as any).xcJson = data => {
res.locals.responseData = data; res.locals.responseData = data;
next(); next();
} };
Promise.resolve(handler.call(this, req, res, next)).catch(err => { Promise.resolve(handler.call(this, req, res, next)).catch(err => {
next(err); next(err);
}); });
}; };
} }
protected abstract postMiddleware(req: Request, res: Response, next: NextFunction): Promise<any>; protected abstract postMiddleware(
req: Request,
res: Response,
next: NextFunction
): Promise<any>;
protected abstract middleware(req: Request, res: Response, next: NextFunction): Promise<any>; protected abstract middleware(
req: Request,
res: Response,
next: NextFunction
): Promise<any>;
abstract get controllerName(): string; abstract get controllerName(): string;
protected replaceEnvVarRec(obj, req): any { protected replaceEnvVarRec(obj, req): any {
return JSON.parse(JSON.stringify(obj), (_key, value) => { return JSON.parse(JSON.stringify(obj), (_key, value) => {
return typeof value === 'string' ? Handlebars.compile(value, {noEscape: true})({ return typeof value === 'string'
req ? Handlebars.compile(value, { noEscape: true })({
}) : value; req
})
: value;
}); });
} }
} }

156
packages/nocodb/src/lib/noco/rest/RestCtrl.ts

@ -1,29 +1,32 @@
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import {NextFunction, Request, Response, Router} from "express"; import { NextFunction, Request, Response, Router } from 'express';
import {Acl, Acls, Route} from "../../../interface/config"; import { Acl, Acls, Route } from '../../../interface/config';
import {BaseModelSql} from "../../dataMapper"; import { BaseModelSql } from '../../dataMapper';
import {RestBaseCtrl} from "./RestBaseCtrl";
import { RestBaseCtrl } from './RestBaseCtrl';
function parseHrtimeToSeconds(hrtime) { function parseHrtimeToSeconds(hrtime) {
const seconds = (hrtime[0] + (hrtime[1] / 1e6)).toFixed(3); const seconds = (hrtime[0] + hrtime[1] / 1e6).toFixed(3);
return seconds; return seconds;
} }
export class RestCtrl extends RestBaseCtrl { export class RestCtrl extends RestBaseCtrl {
public app: any; public app: any;
private table: string; private table: string;
private models: { [key: string]: BaseModelSql }; private models: { [key: string]: BaseModelSql };
private acls: Acls; private acls: Acls;
constructor(
constructor(app: any, models: { [key: string]: BaseModelSql }, table: string, routes: Route[], rootPath: string, acls: Acls, middlewareBody?: string) { app: any,
models: { [key: string]: BaseModelSql },
table: string,
routes: Route[],
rootPath: string,
acls: Acls,
middlewareBody?: string
) {
super(); super();
autoBind(this); autoBind(this);
this.app = app; this.app = app;
@ -36,7 +39,6 @@ export class RestCtrl extends RestBaseCtrl {
this.acls = acls; this.acls = acls;
} }
private get model(): BaseModelSql { private get model(): BaseModelSql {
return this.models?.[this.table]; return this.models?.[this.table];
} }
@ -102,8 +104,14 @@ export class RestCtrl extends RestBaseCtrl {
} }
public async count(req: Request | any, res: Response): Promise<void> { public async count(req: Request | any, res: Response): Promise<void> {
if (req.query.conditionGraph && typeof req.query.conditionGraph === 'string') { if (
req.query.conditionGraph = {models: this.models, condition: JSON.parse(req.query.conditionGraph)} req.query.conditionGraph &&
typeof req.query.conditionGraph === 'string'
) {
req.query.conditionGraph = {
models: this.models,
condition: JSON.parse(req.query.conditionGraph)
};
} }
const data = await req.model.countByPk({ const data = await req.model.countByPk({
...req.query ...req.query
@ -125,7 +133,6 @@ export class RestCtrl extends RestBaseCtrl {
res.json(data); res.json(data);
} }
public async bulkInsert(req: Request | any, res: Response): Promise<void> { public async bulkInsert(req: Request | any, res: Response): Promise<void> {
const data = await req.model.insertb(req.body); const data = await req.model.insertb(req.body);
res.json(data); res.json(data);
@ -137,7 +144,7 @@ export class RestCtrl extends RestBaseCtrl {
} }
public async bulkDelete(req: Request | any, res: Response): Promise<void> { public async bulkDelete(req: Request | any, res: Response): Promise<void> {
const data = await req.model.delb(req.body) const data = await req.model.delb(req.body);
res.json(data); res.json(data);
} }
@ -145,13 +152,19 @@ export class RestCtrl extends RestBaseCtrl {
const startTime = process.hrtime(); const startTime = process.hrtime();
try { try {
if (req.query.conditionGraph && typeof req.query.conditionGraph === 'string') { if (
req.query.conditionGraph = {models: this.models, condition: JSON.parse(req.query.conditionGraph)} req.query.conditionGraph &&
typeof req.query.conditionGraph === 'string'
) {
req.query.conditionGraph = {
models: this.models,
condition: JSON.parse(req.query.conditionGraph)
};
} }
if (req.query.condition && typeof req.query.condition === 'string') { if (req.query.condition && typeof req.query.condition === 'string') {
req.query.condition = JSON.parse(req.query.condition) req.query.condition = JSON.parse(req.query.condition);
} }
}catch (e){ } catch (e) {
/* ignore parse error */ /* ignore parse error */
} }
@ -167,13 +180,19 @@ export class RestCtrl extends RestBaseCtrl {
const startTime = process.hrtime(); const startTime = process.hrtime();
try { try {
if (req.query.conditionGraph && typeof req.query.conditionGraph === 'string') { if (
req.query.conditionGraph = {models: this.models, condition: JSON.parse(req.query.conditionGraph)} req.query.conditionGraph &&
typeof req.query.conditionGraph === 'string'
) {
req.query.conditionGraph = {
models: this.models,
condition: JSON.parse(req.query.conditionGraph)
};
} }
if (req.query.condition && typeof req.query.condition === 'string') { if (req.query.condition && typeof req.query.condition === 'string') {
req.query.condition = JSON.parse(req.query.condition) req.query.condition = JSON.parse(req.query.condition);
} }
}catch (e){ } catch (e) {
/* ignore parse error */ /* ignore parse error */
} }
@ -185,12 +204,17 @@ export class RestCtrl extends RestBaseCtrl {
res.xcJson(data); res.xcJson(data);
} }
public async m2mNotChildren(req: Request | any, res): Promise<void> { public async m2mNotChildren(req: Request | any, res): Promise<void> {
const startTime = process.hrtime(); const startTime = process.hrtime();
if (req.query.conditionGraph && typeof req.query.conditionGraph === 'string') { if (
req.query.conditionGraph = {models: this.models, condition: JSON.parse(req.query.conditionGraph)} req.query.conditionGraph &&
typeof req.query.conditionGraph === 'string'
) {
req.query.conditionGraph = {
models: this.models,
condition: JSON.parse(req.query.conditionGraph)
};
} }
const list = await req.model.m2mNotChildren({ const list = await req.model.m2mNotChildren({
@ -204,32 +228,39 @@ export class RestCtrl extends RestBaseCtrl {
const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime)); const elapsedSeconds = parseHrtimeToSeconds(process.hrtime(startTime));
res.setHeader('xc-db-response', elapsedSeconds); res.setHeader('xc-db-response', elapsedSeconds);
res.xcJson({list, info: count}); res.xcJson({ list, info: count });
} }
protected async middleware(req: Request | any, res: Response, next: NextFunction): Promise<any> { protected async middleware(
req: Request | any,
res: Response,
next: NextFunction
): Promise<any> {
req.model = this.model; req.model = this.model;
req.models = this.models; req.models = this.models;
req.table = this.table; req.table = this.table;
const methodOperationMap = { const methodOperationMap = {
get: 'read', get: 'read',
post: 'create', post: 'create',
put: 'update', put: 'update',
delete: 'delete', delete: 'delete'
} };
const roleOperationPossible = (roles, operation, object) => { const roleOperationPossible = (roles, operation, object) => {
const errors = []; const errors = [];
for (const [roleName, isAllowed] of Object.entries(roles)) { for (const [roleName, isAllowed] of Object.entries(roles)) {
// todo: handle conditions from multiple roles // todo: handle conditions from multiple roles
if (this.acl?.[roleName]?.[operation]?.custom) { if (this.acl?.[roleName]?.[operation]?.custom) {
const condition = this.replaceEnvVarRec(this.acl?.[roleName]?.[operation]?.custom, req); const condition = this.replaceEnvVarRec(
(req as any).query.conditionGraph = {condition, models: this.models}; this.acl?.[roleName]?.[operation]?.custom,
req
);
(req as any).query.conditionGraph = {
condition,
models: this.models
};
} }
if (!isAllowed) { if (!isAllowed) {
@ -241,43 +272,48 @@ export class RestCtrl extends RestBaseCtrl {
if (this.acl[roleName][operation]) { if (this.acl[roleName][operation]) {
return true; return true;
} }
} else if (this.acl?.[roleName]?.[operation] && roleOperationObjectGet(roleName, operation, object)) { } else if (
this.acl?.[roleName]?.[operation] &&
roleOperationObjectGet(roleName, operation, object)
) {
return true; return true;
} }
} catch (e) { } catch (e) {
errors.push(e); errors.push(e);
} }
} }
if (errors?.length) { if (errors?.length) {
throw errors[0] throw errors[0];
} }
return false; return false;
} };
// @ts-ignore // @ts-ignore
const roleOperationObjectGet = (role, operation, object) => { const roleOperationObjectGet = (role, operation, object) => {
const columns = this.acl[role][operation].columns; const columns = this.acl[role][operation].columns;
if (columns) { if (columns) {
// todo: merge allowed columns if multiple roles // todo: merge allowed columns if multiple roles
const allowedCols = Object.keys(columns).filter(col => columns[col]) const allowedCols = Object.keys(columns).filter(col => columns[col]);
res.locals.xcAcl = {allowedCols, operation, columns}; res.locals.xcAcl = { allowedCols, operation, columns };
if (operation === 'update' || operation === 'create') { if (operation === 'update' || operation === 'create') {
if (Array.isArray(object)) { if (Array.isArray(object)) {
for (const row of object) { for (const row of object) {
for (const colInReq of Object.keys(row)) { for (const colInReq of Object.keys(row)) {
if (!allowedCols.includes(colInReq)) { if (!allowedCols.includes(colInReq)) {
throw new Error(`User doesn't have permission to edit '${colInReq}' column`); throw new Error(
`User doesn't have permission to edit '${colInReq}' column`
);
} }
} }
} }
} else { } else {
for (const colInReq of Object.keys(object)) { for (const colInReq of Object.keys(object)) {
if (!allowedCols.includes(colInReq)) { if (!allowedCols.includes(colInReq)) {
throw new Error(`User doesn't have permission to edit '${colInReq}' column`); throw new Error(
`User doesn't have permission to edit '${colInReq}' column`
);
} }
} }
} }
@ -286,21 +322,27 @@ export class RestCtrl extends RestBaseCtrl {
return Object.values(columns).some(Boolean); return Object.values(columns).some(Boolean);
} }
} }
}
const roles = (req as any)?.locals?.user?.roles ?? (req as any)?.session?.passport?.user?.roles ?? {
guest: true
}; };
const roles = (req as any)?.locals?.user?.roles ??
(req as any)?.session?.passport?.user?.roles ?? {
guest: true
};
try { try {
const allowed = roleOperationPossible(roles, methodOperationMap[req.method.toLowerCase()], req.body); const allowed = roleOperationPossible(
roles,
methodOperationMap[req.method.toLowerCase()],
req.body
);
if (allowed) { if (allowed) {
// any additional rules can be made here // any additional rules can be made here
return next(); return next();
} else { } else {
const msg = roles.guest ? `Access Denied : Please Login or Signup for a new account` : `Access Denied for this account`; const msg = roles.guest
? `Access Denied : Please Login or Signup for a new account`
: `Access Denied for this account`;
return res.status(403).json({ return res.status(403).json({
msg msg
}); });
@ -312,9 +354,11 @@ export class RestCtrl extends RestBaseCtrl {
} }
} }
protected async postMiddleware(
protected async postMiddleware(_req: Request, res: Response, _next: NextFunction): Promise<any> { _req: Request,
res: Response,
_next: NextFunction
): Promise<any> {
const data = res.locals.responseData; const data = res.locals.responseData;
if (!res.locals.xcAcl) { if (!res.locals.xcAcl) {
@ -322,7 +366,7 @@ export class RestCtrl extends RestBaseCtrl {
} }
// @ts-ignore // @ts-ignore
const {allowedCols, operation, columns} = res.locals.xcAcl; const { allowedCols, operation, columns } = res.locals.xcAcl;
if (Array.isArray(data)) { if (Array.isArray(data)) {
for (const row of data) { for (const row of data) {
@ -342,11 +386,9 @@ export class RestCtrl extends RestBaseCtrl {
return res.json(data); return res.json(data);
} }
get controllerName(): string { get controllerName(): string {
return this.table; return this.table;
} }
} }
/** /**

141
packages/nocodb/src/lib/noco/rest/RestCtrlBelongsTo.ts

@ -1,25 +1,31 @@
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import {NextFunction, Request, Response} from "express"; import { NextFunction, Request, Response } from 'express';
import {Acl, Acls, Route} from "../../../interface/config"; import { Acl, Acls, Route } from '../../../interface/config';
import {BaseModelSql} from "../../dataMapper"; import { BaseModelSql } from '../../dataMapper';
import { RestBaseCtrl } from './RestBaseCtrl';
import {RestBaseCtrl} from "./RestBaseCtrl";
export class RestCtrlBelongsTo extends RestBaseCtrl { export class RestCtrlBelongsTo extends RestBaseCtrl {
public parentTable: string; public parentTable: string;
public childTable: string; public childTable: string;
public app: any; public app: any;
// public routes: Route[]; // public routes: Route[];
private models: { [key: string]: BaseModelSql }; private models: { [key: string]: BaseModelSql };
private acls: Acls; private acls: Acls;
constructor(app: any, models: { [key: string]: BaseModelSql }, parentTable: string, childTable: string, routes: Route[], rootPath: string, acls: Acls, middlewareBody?: string) { constructor(
app: any,
models: { [key: string]: BaseModelSql },
parentTable: string,
childTable: string,
routes: Route[],
rootPath: string,
acls: Acls,
middlewareBody?: string
) {
super(); super();
autoBind(this); autoBind(this);
this.app = app; this.app = app;
@ -42,7 +48,6 @@ export class RestCtrlBelongsTo extends RestBaseCtrl {
return this.models?.[this.childTable]; return this.models?.[this.childTable];
} }
private get parentAcl(): Acl { private get parentAcl(): Acl {
return this.acls?.[this.parentTable]; return this.acls?.[this.parentTable];
} }
@ -59,7 +64,11 @@ export class RestCtrlBelongsTo extends RestBaseCtrl {
res.xcJson(data); res.xcJson(data);
} }
protected async middleware(req: Request | any, res: Response, next: NextFunction): Promise<any> { protected async middleware(
req: Request | any,
res: Response,
next: NextFunction
): Promise<any> {
req.childModel = this.childModel; req.childModel = this.childModel;
req.parentModel = this.parentModel; req.parentModel = this.parentModel;
req.parentTable = this.parentTable; req.parentTable = this.parentTable;
@ -69,31 +78,45 @@ export class RestCtrlBelongsTo extends RestBaseCtrl {
get: 'read', get: 'read',
post: 'create', post: 'create',
put: 'update', put: 'update',
delete: 'delete', delete: 'delete'
} };
const roleOperationPossible = (roles, operation, object) => { const roleOperationPossible = (roles, operation, object) => {
const errors = []; const errors = [];
res.locals.xcAcl = {operation}; res.locals.xcAcl = { operation };
for (const [roleName, isAllowed] of Object.entries(roles)) { for (const [roleName, isAllowed] of Object.entries(roles)) {
// todo: handling conditions from multiple roles // todo: handling conditions from multiple roles
if (this.childAcl?.[roleName]?.[operation]?.custom) { if (this.childAcl?.[roleName]?.[operation]?.custom) {
// req.query.condition = replaceEnvVarRec(this.acl?.[roleName]?.[operation]?.custom) // req.query.condition = replaceEnvVarRec(this.acl?.[roleName]?.[operation]?.custom)
const condition = this.replaceEnvVarRec(this.childAcl?.[roleName]?.[operation]?.custom,req); const condition = this.replaceEnvVarRec(
(req as any).query.childNestedCondition = {condition, models: this.models}; this.childAcl?.[roleName]?.[operation]?.custom,
req
);
(req as any).query.childNestedCondition = {
condition,
models: this.models
};
} }
if (this.parentAcl?.[roleName]?.[operation]?.custom) { if (this.parentAcl?.[roleName]?.[operation]?.custom) {
// req.query.condition = replaceEnvVarRec(this.acl?.[roleName]?.[operation]?.custom) // req.query.condition = replaceEnvVarRec(this.acl?.[roleName]?.[operation]?.custom)
const condition = this.replaceEnvVarRec(this.parentAcl?.[roleName]?.[operation]?.custom,req); const condition = this.replaceEnvVarRec(
(req as any).query.conditionGraph = {condition, models: this.models}; this.parentAcl?.[roleName]?.[operation]?.custom,
req
);
(req as any).query.conditionGraph = {
condition,
models: this.models
};
} }
const childColumns = this.childAcl[roleName]?.[operation]?.columns; const childColumns = this.childAcl[roleName]?.[operation]?.columns;
if (childColumns) { if (childColumns) {
const allowedChildCols = Object.keys(childColumns).filter(col => childColumns[col]); const allowedChildCols = Object.keys(childColumns).filter(
res.locals.xcAcl.allowedChildCols = res.locals.xcAcl.allowedChildCols || []; col => childColumns[col]
);
res.locals.xcAcl.allowedChildCols =
res.locals.xcAcl.allowedChildCols || [];
res.locals.xcAcl.allowedChildCols.push(...allowedChildCols); res.locals.xcAcl.allowedChildCols.push(...allowedChildCols);
res.locals.xcAcl.childColumns = childColumns; res.locals.xcAcl.childColumns = childColumns;
} }
@ -107,7 +130,10 @@ export class RestCtrlBelongsTo extends RestBaseCtrl {
if (this.parentAcl[roleName][operation]) { if (this.parentAcl[roleName][operation]) {
return true; return true;
} }
} else if (this.parentAcl?.[roleName]?.[operation] && roleOperationObjectGet(roleName, operation, object)) { } else if (
this.parentAcl?.[roleName]?.[operation] &&
roleOperationObjectGet(roleName, operation, object)
) {
return true; return true;
} }
} catch (e) { } catch (e) {
@ -115,10 +141,10 @@ export class RestCtrlBelongsTo extends RestBaseCtrl {
} }
} }
if (errors?.length) { if (errors?.length) {
throw errors[0] throw errors[0];
} }
return false; return false;
} };
// @ts-ignore // @ts-ignore
const roleOperationObjectGet = (role, operation, object) => { const roleOperationObjectGet = (role, operation, object) => {
@ -126,28 +152,38 @@ export class RestCtrlBelongsTo extends RestBaseCtrl {
if (columns) { if (columns) {
// todo: merge allowed columns if multiple roles // todo: merge allowed columns if multiple roles
const allowedParentCols = Object.keys(columns).filter(col => columns[col]); const allowedParentCols = Object.keys(columns).filter(
Object.assign(res.locals.xcAcl, {allowedParentCols, parentColumns: columns}) col => columns[col]
);
Object.assign(res.locals.xcAcl, {
allowedParentCols,
parentColumns: columns
});
return Object.values(columns).some(Boolean); return Object.values(columns).some(Boolean);
} }
} };
console.log(`${this.parentModel.tn}Hm${this.childModel.tn} middleware`) console.log(`${this.parentModel.tn}Hm${this.childModel.tn} middleware`);
const roles = (req as any)?.locals?.user?.roles ??
const roles = (req as any)?.locals?.user?.roles ?? (req as any)?.session?.passport?.user?.roles ?? { (req as any)?.session?.passport?.user?.roles ?? {
guest: true guest: true
}; };
try { try {
const allowed = roleOperationPossible(roles, methodOperationMap[req.method.toLowerCase()], req.body); const allowed = roleOperationPossible(
roles,
methodOperationMap[req.method.toLowerCase()],
req.body
);
if (allowed) { if (allowed) {
// any additional rules can be made here // any additional rules can be made here
return next(); return next();
} else { } else {
const msg = roles.guest ? `Access Denied : Please Login or Signup for a new account` : `Access Denied for this account`; const msg = roles.guest
? `Access Denied : Please Login or Signup for a new account`
: `Access Denied for this account`;
return res.status(403).json({ return res.status(403).json({
msg msg
}); });
@ -159,20 +195,28 @@ export class RestCtrlBelongsTo extends RestBaseCtrl {
} }
} }
protected async postMiddleware(
protected async postMiddleware(req: Request, res: Response, _next: NextFunction): Promise<any> { req: Request,
res: Response,
_next: NextFunction
): Promise<any> {
const data = res.locals.responseData; const data = res.locals.responseData;
if (!res.locals.xcAcl) { if (!res.locals.xcAcl) {
return res.json(data); return res.json(data);
} }
// @ts-ignore // @ts-ignore
const {allowedChildCols, operation, allowedParentCols, parentColumns, childColumns} = res.locals.xcAcl; const {
allowedChildCols,
const isBt = req.url.toLowerCase().startsWith('/belongs/' + this.parentTable.toLowerCase()); operation,
allowedParentCols,
parentColumns,
childColumns
} = res.locals.xcAcl;
const isBt = req.url
.toLowerCase()
.startsWith('/belongs/' + this.parentTable.toLowerCase());
if ((!allowedChildCols || !isBt) && !allowedParentCols) { if ((!allowedChildCols || !isBt) && !allowedParentCols) {
return res.json(data); return res.json(data);
@ -191,10 +235,18 @@ export class RestCtrlBelongsTo extends RestBaseCtrl {
delete row[colInRes][colInChild]; delete row[colInRes][colInChild];
} }
} }
} else if (childColumns && colInRes in childColumns && !childColumns[colInRes]) { } else if (
childColumns &&
colInRes in childColumns &&
!childColumns[colInRes]
) {
delete row[colInRes]; delete row[colInRes];
} }
} else if (childColumns && colInRes in childColumns && !childColumns[colInRes]) { } else if (
childColumns &&
colInRes in childColumns &&
!childColumns[colInRes]
) {
delete row[colInRes]; delete row[colInRes];
} }
} }
@ -209,7 +261,6 @@ export class RestCtrlBelongsTo extends RestBaseCtrl {
return res.json(data); return res.json(data);
} }
get controllerName(): string { get controllerName(): string {
return `${this.childTable}.bt.${this.parentTable}`; return `${this.childTable}.bt.${this.parentTable}`;
} }

33
packages/nocodb/src/lib/noco/rest/RestCtrlCustom.ts

@ -1,20 +1,22 @@
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import {NextFunction, Request, Response, Router} from "express"; import { NextFunction, Request, Response, Router } from 'express';
import {Route} from "../../../interface/config"; import { Route } from '../../../interface/config';
import {BaseModelSql} from "../../dataMapper"; import { BaseModelSql } from '../../dataMapper';
import {RestBaseCtrl} from "./RestBaseCtrl"; import { RestBaseCtrl } from './RestBaseCtrl';
export class RestCtrlCustom extends RestBaseCtrl { export class RestCtrlCustom extends RestBaseCtrl {
public app: any; public app: any;
protected models: { [key: string]: BaseModelSql }; protected models: { [key: string]: BaseModelSql };
constructor(
constructor(app: any, models: { [key: string]: BaseModelSql }, routes: Route[], middlewareBody?: string) { app: any,
models: { [key: string]: BaseModelSql },
routes: Route[],
middlewareBody?: string
) {
super(); super();
autoBind(this); autoBind(this);
this.app = app; this.app = app;
@ -25,18 +27,25 @@ export class RestCtrlCustom extends RestBaseCtrl {
this.rootPath = ''; this.rootPath = '';
} }
protected async middleware(_req: Request, _res: Response, next: NextFunction): Promise<any> { protected async middleware(
_req: Request,
_res: Response,
next: NextFunction
): Promise<any> {
next(); next();
// return Promise.resolve(undefined); // return Promise.resolve(undefined);
} }
protected postMiddleware(_req: Request, _res: Response, _next: NextFunction): Promise<any> { protected postMiddleware(
_req: Request,
_res: Response,
_next: NextFunction
): Promise<any> {
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
get controllerName(): string { get controllerName(): string {
return "__xc_custom"; return '__xc_custom';
} }
} }
/** /**

146
packages/nocodb/src/lib/noco/rest/RestCtrlHasMany.ts

@ -1,24 +1,30 @@
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import {NextFunction, Request, Response} from "express"; import { NextFunction, Request, Response } from 'express';
import {Acl, Acls, Route} from "../../../interface/config"; import { Acl, Acls, Route } from '../../../interface/config';
import {BaseModelSql} from "../../dataMapper"; import { BaseModelSql } from '../../dataMapper';
import { RestBaseCtrl } from './RestBaseCtrl';
import {RestBaseCtrl} from "./RestBaseCtrl";
export class RestCtrlHasMany extends RestBaseCtrl { export class RestCtrlHasMany extends RestBaseCtrl {
public parentTable: string; public parentTable: string;
public childTable: string; public childTable: string;
public app: any; public app: any;
// public routes: Route[]; // public routes: Route[];
private models: { [key: string]: BaseModelSql }; private models: { [key: string]: BaseModelSql };
private acls: Acls; private acls: Acls;
constructor(app: any, models: { [key: string]: BaseModelSql }, parentTable: string, childTable: string, routes: Route[], rootPath: string, acls: Acls, middlewareBody?: string) { constructor(
app: any,
models: { [key: string]: BaseModelSql },
parentTable: string,
childTable: string,
routes: Route[],
rootPath: string,
acls: Acls,
middlewareBody?: string
) {
super(); super();
autoBind(this); autoBind(this);
this.app = app; this.app = app;
@ -114,13 +120,12 @@ export class RestCtrlHasMany extends RestBaseCtrl {
res.json(data); res.json(data);
} }
public async list(req: Request | any, res): Promise<void> { public async list(req: Request | any, res): Promise<void> {
const data = await req.parentModel.hasManyChildren({ const data = await req.parentModel.hasManyChildren({
child: req.childModel.tn, child: req.childModel.tn,
...req.params, ...req.params,
...req.query ...req.query
} as any) } as any);
res.xcJson(data); res.xcJson(data);
} }
@ -132,8 +137,11 @@ export class RestCtrlHasMany extends RestBaseCtrl {
res.xcJson(data); res.xcJson(data);
} }
protected async middleware(req: Request | any, res: Response, next: NextFunction): Promise<any> { protected async middleware(
req: Request | any,
res: Response,
next: NextFunction
): Promise<any> {
req.childModel = this.childModel; req.childModel = this.childModel;
req.parentModel = this.parentModel; req.parentModel = this.parentModel;
req.parentTable = this.parentTable; req.parentTable = this.parentTable;
@ -143,30 +151,43 @@ export class RestCtrlHasMany extends RestBaseCtrl {
get: 'read', get: 'read',
post: 'create', post: 'create',
put: 'update', put: 'update',
delete: 'delete', delete: 'delete'
} };
const roleOperationPossible = (roles, operation, object) => { const roleOperationPossible = (roles, operation, object) => {
const errors = []; const errors = [];
res.locals.xcAcl = {operation}; res.locals.xcAcl = { operation };
for (const [roleName, isAllowed] of Object.entries(roles)) { for (const [roleName, isAllowed] of Object.entries(roles)) {
if (this.childAcl?.[roleName]?.[operation]?.custom) { if (this.childAcl?.[roleName]?.[operation]?.custom) {
const condition = this.replaceEnvVarRec(this.childAcl?.[roleName]?.[operation]?.custom, req); const condition = this.replaceEnvVarRec(
(req as any).query.conditionGraph = {condition, models: this.models}; this.childAcl?.[roleName]?.[operation]?.custom,
req
);
(req as any).query.conditionGraph = {
condition,
models: this.models
};
} }
if (this.parentAcl?.[roleName]?.[operation]?.custom) { if (this.parentAcl?.[roleName]?.[operation]?.custom) {
const condition = this.replaceEnvVarRec(this.parentAcl?.[roleName]?.[operation]?.custom, req); const condition = this.replaceEnvVarRec(
(req as any).query.parentNestedCondition = {condition, models: this.models}; this.parentAcl?.[roleName]?.[operation]?.custom,
req
);
(req as any).query.parentNestedCondition = {
condition,
models: this.models
};
} }
const parentColumns = this.parentAcl?.[roleName]?.[operation]?.columns; const parentColumns = this.parentAcl?.[roleName]?.[operation]?.columns;
if (parentColumns) { if (parentColumns) {
const allowedParentCols = Object.keys(parentColumns).filter(col => parentColumns[col]); const allowedParentCols = Object.keys(parentColumns).filter(
res.locals.xcAcl.allowedParentCols = res.locals.xcAcl.allowedParentCols || []; col => parentColumns[col]
);
res.locals.xcAcl.allowedParentCols =
res.locals.xcAcl.allowedParentCols || [];
res.locals.xcAcl.allowedParentCols.push(...allowedParentCols); res.locals.xcAcl.allowedParentCols.push(...allowedParentCols);
// todo: merge columns // todo: merge columns
@ -182,7 +203,10 @@ export class RestCtrlHasMany extends RestBaseCtrl {
if (this.childAcl?.[roleName]?.[operation]) { if (this.childAcl?.[roleName]?.[operation]) {
return true; return true;
} }
} else if (this.childAcl?.[roleName]?.[operation] && roleOperationObjectGet(roleName, operation, object)) { } else if (
this.childAcl?.[roleName]?.[operation] &&
roleOperationObjectGet(roleName, operation, object)
) {
return true; return true;
} }
} catch (e) { } catch (e) {
@ -190,10 +214,10 @@ export class RestCtrlHasMany extends RestBaseCtrl {
} }
} }
if (errors?.length) { if (errors?.length) {
throw errors[0] throw errors[0];
} }
return false; return false;
} };
// @ts-ignore // @ts-ignore
const roleOperationObjectGet = (role, operation, object) => { const roleOperationObjectGet = (role, operation, object) => {
@ -201,22 +225,31 @@ export class RestCtrlHasMany extends RestBaseCtrl {
if (columns) { if (columns) {
// todo: merge allowed columns if multiple roles // todo: merge allowed columns if multiple roles
const allowedChildCols = Object.keys(columns).filter(col => columns[col]); const allowedChildCols = Object.keys(columns).filter(
Object.assign(res.locals.xcAcl, {allowedChildCols, childColumns: columns}) col => columns[col]
);
Object.assign(res.locals.xcAcl, {
allowedChildCols,
childColumns: columns
});
if (operation === 'update' || operation === 'create') { if (operation === 'update' || operation === 'create') {
if (Array.isArray(object)) { if (Array.isArray(object)) {
for (const row of object) { for (const row of object) {
for (const colInReq of Object.keys(row)) { for (const colInReq of Object.keys(row)) {
if (!allowedChildCols.includes(colInReq)) { if (!allowedChildCols.includes(colInReq)) {
throw new Error(`User doesn't have permission to edit '${colInReq}' column`); throw new Error(
`User doesn't have permission to edit '${colInReq}' column`
);
} }
} }
} }
} else { } else {
for (const colInReq of Object.keys(object)) { for (const colInReq of Object.keys(object)) {
if (!allowedChildCols.includes(colInReq)) { if (!allowedChildCols.includes(colInReq)) {
throw new Error(`User doesn't have permission to edit '${colInReq}' column`); throw new Error(
`User doesn't have permission to edit '${colInReq}' column`
);
} }
} }
} }
@ -225,23 +258,28 @@ export class RestCtrlHasMany extends RestBaseCtrl {
return Object.values(columns).some(Boolean); return Object.values(columns).some(Boolean);
} }
} }
} };
console.log(`${this.parentModel.tn}Hm${this.childModel.tn} middleware`)
console.log(`${this.parentModel.tn}Hm${this.childModel.tn} middleware`);
const roles = (req as any)?.locals?.user?.roles ?? (req as any)?.session?.passport?.user?.roles ?? { const roles = (req as any)?.locals?.user?.roles ??
guest: true (req as any)?.session?.passport?.user?.roles ?? {
}; guest: true
};
try { try {
const allowed = roleOperationPossible(roles, methodOperationMap[req.method.toLowerCase()], req.body); const allowed = roleOperationPossible(
roles,
methodOperationMap[req.method.toLowerCase()],
req.body
);
if (allowed) { if (allowed) {
// any additional rules can be made here // any additional rules can be made here
return next(); return next();
} else { } else {
const msg = roles.guest ? `Access Denied : Please Login or Signup for a new account` : `Access Denied for this account`; const msg = roles.guest
? `Access Denied : Please Login or Signup for a new account`
: `Access Denied for this account`;
return res.status(403).json({ return res.status(403).json({
msg msg
}); });
@ -253,20 +291,22 @@ export class RestCtrlHasMany extends RestBaseCtrl {
} }
} }
protected async postMiddleware(
protected async postMiddleware(req: Request, res: Response, _next: NextFunction): Promise<any> { req: Request,
res: Response,
_next: NextFunction
): Promise<any> {
const data = res.locals.responseData; const data = res.locals.responseData;
if (!res.locals.xcAcl) { if (!res.locals.xcAcl) {
return res.json(data); return res.json(data);
} }
// @ts-ignore // @ts-ignore
const {operation, parentColumns, childColumns} = res.locals.xcAcl; const { operation, parentColumns, childColumns } = res.locals.xcAcl;
const isHm = req.url.toLowerCase().startsWith('/has/' + this.childTable.toLowerCase());
const isHm = req.url
.toLowerCase()
.startsWith('/has/' + this.childTable.toLowerCase());
if ((!parentColumns || !isHm) && !childColumns) { if ((!parentColumns || !isHm) && !childColumns) {
return res.json(data); return res.json(data);
@ -287,10 +327,18 @@ export class RestCtrlHasMany extends RestBaseCtrl {
} }
} }
} }
} else if (parentColumns && colInRes in parentColumns && !parentColumns[colInRes]) { } else if (
parentColumns &&
colInRes in parentColumns &&
!parentColumns[colInRes]
) {
delete row[colInRes]; delete row[colInRes];
} }
} else if (childColumns && colInRes in childColumns && !childColumns[colInRes]) { } else if (
childColumns &&
colInRes in childColumns &&
!childColumns[colInRes]
) {
delete row[colInRes]; delete row[colInRes];
} }
} }
@ -306,11 +354,9 @@ export class RestCtrlHasMany extends RestBaseCtrl {
return res.json(data); return res.json(data);
} }
get controllerName(): string { get controllerName(): string {
return `${this.parentTable}.hm.${this.childTable}`; return `${this.parentTable}.hm.${this.childTable}`;
} }
} }
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save