Upgrade to v7

The purpose of this note is to help developers to upgrade their liana from v6 to v7. Please read carefully and integrate the following breaking changes to ensure a smooth upgrade.​

Upgrading to v7

This upgrade unlocks the following feature:

  • easier addition of additional databases

  • no need to re-authenticate when switching between projects/environments/team

  • dynamic smart action forms

Before upgrading to v7, please take note of the following requirement:

  • express must be version 4.17 or higher

Also please consider the below breaking changes.

As for any dependency upgrade, it's very important to test this upgrade in your testing environments. Not doing so could result in your admin panel being unusable.

To upgrade to v7, simply run:

SQL
Mongodb
SQL
npm install [email protected]
Mongodb
npm install [email protected]

In case of a regression introduced in Production after the upgrade, a rollback to your previous liana is the fastest way to restore your admin panel.

Breaking changes

Liana initialization: deprecated parameters

The following list of parameters of Liana.init will be ignored with a warning:

  • onlyCrudModule

  • modelsDir

  • sequelize/mongoose

Your project will still start, but should show a warning - and these configurations are just ignored.

The following list of parameters of Liana.init will show an error, and are not supported anymore in v7:

  • secretKey

  • authKey

They should be replaced by envSecret and authSecret respectivelly.

Liana initialization: new parameters

The following changes are intended to ease the addition of new databases to your project.

middlewares/forestadmin.js

The liana initialization now takes objectMapping and connections as parameters (see lines 8 and 9).

middlewares/forestadmin.js
const { objectMapping, connections } = require('../models');
module.exports = async function forestadmin(app) {
app.use(await Liana.init({
configDir: path.join(__dirname, '../forest'),
envSecret: process.env.FOREST_ENV_SECRET,
authSecret: process.env.FOREST_AUTH_SECRET,
objectMapping,
connections,
}));
console.log(chalk.cyan('Your admin panel is available here: https://app.forestadmin.com/projects'));
};
  • objectMapping represents the static instance of your object mapper (require('sequelize') or require('mongoose'))

  • connections is a map of your existing connections, indexed by a unique name for each connections

models/index.js

The models/index.js should be updated as well, in order to export objectMapping & connections

SQL
Mongodb
SQL
models/index.js
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const databasesConfiguration = require('../config/databases');
const connections = {};
const db = {};
databasesConfiguration.forEach((databaseInfo) => {
const connection = new Sequelize(databaseInfo.connection.url, databaseInfo.connection.options);
connections[databaseInfo.name] = connection;
const modelsDir = databaseInfo.modelsDir || path.join(__dirname, databaseInfo.name);
fs
.readdirSync(modelsDir)
.filter((file) => file.indexOf('.') !== 0 && file !== 'index.js')
.forEach((file) => {
try {
const model = connection.import(path.join(modelsDir, file));
db[model.name] = model;
} catch (error) {
console.error('Model creation error: ' + error);
}
});
});
Object.keys(db).forEach((modelName) => {
if ('associate' in db[modelName]) {
db[modelName].associate(db);
}
});
db.objectMapping = Sequelize;
db.connections = connections;
module.exports = db;
Mongodb
models/index.js
const fs = require('fs');
const path = require('path');
const Mongoose = require('mongoose');
const databasesConfiguration = require('../config/databases');
const connections = {};
const db = {};
databasesConfiguration.forEach((databaseInfo) => {
const connection = Mongoose.createConnection(databaseInfo.connection.url, databaseInfo.connection.options);
connections[databaseInfo.name] = connection;
const modelsDir = databaseInfo.modelsDir || path.join(__dirname, databaseInfo.name);
fs
.readdirSync(modelsDir)
.filter((file) => file.indexOf('.') !== 0 && file !== 'index.js')
.forEach((file) => {
try {
const model = require(path.join(modelsDir, file))(connection, Mongoose);
db[model.modelName] = model;
} catch (error) {
console.error(`Model creation error: ${error}`);
}
});
});
db.objectMapping = Mongoose;
db.connections = connections;
module.exports = db;

config/databases.js

The config/databases.js should be added as follows:

SQL
Mongodb
SQL
const path = require('path');
const databaseOptions = {
logging: !process.env.NODE_ENV || process.env.NODE_ENV === 'development' ? console.log : false,
pool: { maxConnections: 10, minConnections: 1 },
dialectOptions: {},
};
if (process.env.DATABASE_SSL && JSON.parse(process.env.DATABASE_SSL.toLowerCase())) {
const rejectUnauthorized = process.env.DATABASE_REJECT_UNAUTHORIZED;
if (rejectUnauthorized && (JSON.parse(rejectUnauthorized.toLowerCase()) === false)) {
databaseOptions.dialectOptions.ssl = { rejectUnauthorized: false };
} else {
databaseOptions.dialectOptions.ssl = true;
}
}
module.exports = [{
name: 'default',
modelsDir: path.resolve(__dirname, '../models'),
connection: {
url: process.env.DATABASE_URL,
options: { ...databaseOptions },
},
}];
Mongodb
const path = require('path');
const databaseOptions = {
useNewUrlParser: true,
useUnifiedTopology: true,
};
module.exports = [{
name: 'default',
modelsDir: path.resolve(__dirname, '../models'),
connection: {
url: process.env.DATABASE_URL,
options: { ...databaseOptions },
},
}];

Calling sequelize with on of the 2 following syntaxes will not work anymore:

const { sequelize } = require('../models');

const models = require('../models'); then using models.sequelize

If you made heavy modifications to the above 2 files and want to avoid editing them, you may - if you only have 1 database - use the following snippet (see lines 11 and 12):

middlewares/forestadmin.js
const chalk = require('chalk');
const path = require('path');
const Liana = require('forest-express-sequelize');
const { sequelize } = require('../models');
module.exports = async function (app) {
app.use(await Liana.init({
configDir: path.join(__dirname, '../forest'),
envSecret: process.env.FOREST_ENV_SECRET,
authSecret: process.env.FOREST_AUTH_SECRET,
objectMapping: sequelize.Sequelize, // Sequelize was already exported in v6
connections: { default: sequelize }, // sequelize was already a connection in v6
}));
console.log(chalk.cyan('Your admin panel is available here: https://app.forestadmin.com/projects'));
};

Mongoose specific changes

If you made the above recommended changes in your models/index.js file, your Mongoose model files should now be written this way:

/models/companies.js
// Models are now returned from a function
module.exports = (mongoose, Mongoose) => {
const schema = Mongoose.Schema({
'country': String,
...
});
return mongoose.model('companies', schema, 'companies');
};

Easier authentication

New environment variable

A new environment variable is required: APPLICATION_URL=http://localhost:3310 must be added to your .env file.

http://localhost:3310 is the default value. The given url must match the remote url if used for a remote environment.

New CORS condition

A change in your app.js is required to modify how CORS are handled. The value 'null' must be accepted for authentication endpoints (lines 11-17).

let allowedOrigins = [/\.forestadmin\.com$/, /localhost:\d{4}$/];
if (process.env.CORS_ORIGINS) {
allowedOrigins = allowedOrigins.concat(process.env.CORS_ORIGINS.split(','));
}
const corsConfig = {
origin: allowedOrigins,
allowedHeaders: ['Authorization', 'X-Requested-With', 'Content-Type'],
maxAge: 86400, // NOTICE: 1 day
credentials: true,
};
app.use('/forest/authentication', cors({
...corsConfig,
// The null origin is sent by browsers for redirected AJAX calls
// we need to support this in authentication routes because OIDC
// redirects to the callback route
origin: corsConfig.origin.concat('null')
}));
app.use(cors(corsConfig));

Setup a static clientId

First you will need to obtain a Client ID for your environment by running the following command:

curl -H "Content-Type: application/json" \
-H "Authorization: Bearer FOREST_ENV_SECRET" \
-X POST \
-d '{"token_endpoint_auth_method": "none", "redirect_uris": ["APPLICATION_URL/forest/authentication/callback"]}' \
https://api.forestadmin.com/oidc/reg

Then assign the client_id value from the response (it's a JWT) to a FOREST_CLIENT_ID variable in your .env file.

This is required if you're running multiple instances of your agent, (with a load balancer for example).

Important Notice

Changelogs

This release note covers only the major changes. To learn more, please refer to the changelogs in our different repositories: