mirror of
https://github.com/plankanban/planka.git
synced 2025-12-24 09:15:01 +03:00
74
server/db/create-admin-user.js
Normal file
74
server/db/create-admin-user.js
Normal file
@@ -0,0 +1,74 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const { read } = require('read');
|
||||
const initKnex = require('knex');
|
||||
|
||||
const knexfile = require('./knexfile');
|
||||
|
||||
const knex = initKnex(knexfile);
|
||||
|
||||
const input = async (fieldName, options = {}) => {
|
||||
const readOptions = {
|
||||
prompt: `${fieldName}${!options.isRequired ? ' (optional)' : ''}: `,
|
||||
};
|
||||
|
||||
if (options.isPassword) {
|
||||
Object.assign(readOptions, {
|
||||
silent: true,
|
||||
replace: '*',
|
||||
});
|
||||
}
|
||||
|
||||
let value;
|
||||
while (!value) {
|
||||
value = await read(readOptions);
|
||||
|
||||
if (!options.isPassword) {
|
||||
value = value.trim();
|
||||
}
|
||||
|
||||
if (options.isRequired && !value) {
|
||||
console.log(`${fieldName} cannot be blank!`);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await knex.migrate.latest();
|
||||
|
||||
process.env.DEFAULT_ADMIN_EMAIL = await input('Email', {
|
||||
isRequired: true,
|
||||
});
|
||||
|
||||
process.env.DEFAULT_ADMIN_PASSWORD = await input('Password', {
|
||||
isRequired: true,
|
||||
isPassword: true,
|
||||
});
|
||||
|
||||
process.env.DEFAULT_ADMIN_NAME = await input('Name', {
|
||||
isRequired: true,
|
||||
});
|
||||
|
||||
process.env.DEFAULT_ADMIN_USERNAME = await input('Username');
|
||||
|
||||
await knex.seed.run({
|
||||
specific: 'default.js',
|
||||
});
|
||||
} catch (error) {
|
||||
process.exitCode = 1;
|
||||
throw error;
|
||||
} finally {
|
||||
knex.destroy();
|
||||
}
|
||||
})();
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const initKnex = require('knex');
|
||||
|
||||
const knexfile = require('./knexfile');
|
||||
@@ -10,7 +15,6 @@ const knex = initKnex(knexfile);
|
||||
await knex.seed.run();
|
||||
} catch (error) {
|
||||
process.exitCode = 1;
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
knex.destroy();
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const dotenv = require('dotenv');
|
||||
const _ = require('lodash');
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.raw(`
|
||||
CREATE SEQUENCE next_id_seq;
|
||||
CREATE FUNCTION next_id(OUT id BIGINT) AS $$
|
||||
DECLARE
|
||||
shard INT := 1;
|
||||
epoch BIGINT := 1567191600000;
|
||||
sequence BIGINT;
|
||||
milliseconds BIGINT;
|
||||
BEGIN
|
||||
SELECT nextval('next_id_seq') % 1024 INTO sequence;
|
||||
SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) INTO milliseconds;
|
||||
id := (milliseconds - epoch) << 23;
|
||||
id := id | (shard << 10);
|
||||
id := id | (sequence);
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
`);
|
||||
|
||||
module.exports.down = (knex) =>
|
||||
knex.raw(`
|
||||
DROP SEQUENCE next_id_seq;
|
||||
DROP FUNCTION next_id(OUT id BIGINT);
|
||||
`);
|
||||
@@ -1,19 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('archive', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.text('from_model').notNullable();
|
||||
table.bigInteger('original_record_id').notNullable();
|
||||
table.json('original_record').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['from_model', 'original_record_id']);
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('archive');
|
||||
@@ -1,29 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema
|
||||
.createTable('user_account', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.text('email').notNullable();
|
||||
table.text('password').notNullable();
|
||||
table.boolean('is_admin').notNullable();
|
||||
table.text('name').notNullable();
|
||||
table.text('username');
|
||||
table.text('avatar_dirname');
|
||||
table.text('phone');
|
||||
table.text('organization');
|
||||
table.boolean('subscribe_to_own_cards').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
table.timestamp('deleted_at', true);
|
||||
})
|
||||
.raw(
|
||||
'ALTER TABLE "user_account" ADD CONSTRAINT "user_email_unique" EXCLUDE ("email" WITH =) WHERE ("deleted_at" IS NULL)',
|
||||
)
|
||||
.raw(
|
||||
'ALTER TABLE "user_account" ADD CONSTRAINT "user_username_unique" EXCLUDE ("username" WITH =) WHERE ("username" IS NOT NULL AND "deleted_at" IS NULL)',
|
||||
);
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('user_account');
|
||||
@@ -1,15 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('project', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.text('name').notNullable();
|
||||
table.jsonb('background');
|
||||
table.text('background_image_dirname');
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('project');
|
||||
@@ -1,19 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('project_manager', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('project_id').notNullable();
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['project_id', 'user_id']);
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('project_manager');
|
||||
@@ -1,22 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('board', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('project_id').notNullable();
|
||||
|
||||
table.text('type').notNullable();
|
||||
table.specificType('position', 'double precision').notNullable();
|
||||
table.text('name').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('project_id');
|
||||
table.index('position');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('board');
|
||||
@@ -1,19 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('board_membership', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('board_id').notNullable();
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['board_id', 'user_id']);
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('board_membership');
|
||||
@@ -1,20 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('label', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('board_id').notNullable();
|
||||
|
||||
table.text('name');
|
||||
table.text('color').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('board_id');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('label');
|
||||
@@ -1,21 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('list', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('board_id').notNullable();
|
||||
|
||||
table.specificType('position', 'double precision').notNullable();
|
||||
table.text('name').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('board_id');
|
||||
table.index('position');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('list');
|
||||
@@ -1,28 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('card', async (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('board_id').notNullable();
|
||||
table.bigInteger('list_id');
|
||||
table.bigInteger('creator_user_id').notNullable();
|
||||
table.bigInteger('cover_attachment_id');
|
||||
|
||||
table.specificType('position', 'double precision');
|
||||
table.text('name').notNullable();
|
||||
table.text('description');
|
||||
table.timestamp('due_date', true);
|
||||
table.jsonb('timer');
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('board_id');
|
||||
table.index('list_id');
|
||||
table.index('position');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('card');
|
||||
@@ -1,21 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('card_subscription', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.boolean('is_permanent').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['card_id', 'user_id']);
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('card_subscription');
|
||||
@@ -1,19 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('card_membership', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['card_id', 'user_id']);
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('card_membership');
|
||||
@@ -1,19 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('card_label', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
table.bigInteger('label_id').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['card_id', 'label_id']);
|
||||
table.index('label_id');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('card_label');
|
||||
@@ -1,20 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('task', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
|
||||
table.text('name').notNullable();
|
||||
table.boolean('is_completed').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('card_id');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('task');
|
||||
@@ -1,23 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('attachment', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
table.bigInteger('creator_user_id').notNullable();
|
||||
|
||||
table.text('dirname').notNullable();
|
||||
table.text('filename').notNullable();
|
||||
table.boolean('is_image').notNullable();
|
||||
table.text('name').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('card_id');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('attachment');
|
||||
@@ -1,21 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('action', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.text('type').notNullable();
|
||||
table.jsonb('data').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('card_id');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('action');
|
||||
@@ -1,24 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('notification', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('user_id').notNullable();
|
||||
table.bigInteger('action_id').notNullable();
|
||||
table.bigInteger('card_id').notNullable();
|
||||
|
||||
table.boolean('is_read').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('user_id');
|
||||
table.index('action_id');
|
||||
table.index('card_id');
|
||||
table.index('is_read');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('notification');
|
||||
@@ -1,71 +0,0 @@
|
||||
const path = require('path');
|
||||
const sharp = require('sharp');
|
||||
const _ = require('lodash');
|
||||
|
||||
const getConfig = require('../../get-config');
|
||||
|
||||
module.exports.up = async (knex) => {
|
||||
await knex.schema.table('attachment', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.jsonb('image');
|
||||
});
|
||||
|
||||
const config = await getConfig();
|
||||
const attachments = await knex('attachment');
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const attachment of attachments) {
|
||||
if (attachment.is_image) {
|
||||
const image = sharp(
|
||||
path.join(config.custom.attachmentsPath, attachment.dirname, attachment.filename),
|
||||
);
|
||||
|
||||
let metadata;
|
||||
try {
|
||||
metadata = await image.metadata(); // eslint-disable-line no-await-in-loop
|
||||
} catch (error) {
|
||||
continue; // eslint-disable-line no-continue
|
||||
}
|
||||
|
||||
if (!['svg', 'pdf'].includes(metadata.format)) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await knex('attachment')
|
||||
.update({
|
||||
image: _.pick(metadata, ['width', 'height']),
|
||||
})
|
||||
.where('id', attachment.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return knex.schema.table('attachment', (table) => {
|
||||
table.dropColumn('is_image');
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.down = async (knex) => {
|
||||
await knex.schema.table('attachment', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.boolean('is_image');
|
||||
});
|
||||
|
||||
const attachments = await knex('attachment');
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const attachment of attachments) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await knex('attachment')
|
||||
.update({
|
||||
is_image: !!attachment.image,
|
||||
})
|
||||
.where('id', attachment.id);
|
||||
}
|
||||
|
||||
return knex.schema.table('attachment', (table) => {
|
||||
table.dropColumn('image');
|
||||
|
||||
table.dropNullable('is_image');
|
||||
});
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
const { addPosition, removePosition } = require('../../utils/migrations');
|
||||
|
||||
module.exports.up = (knex) => addPosition(knex, 'task', 'card_id');
|
||||
|
||||
module.exports.down = (knex) => removePosition(knex, 'task');
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.table('user_account', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.text('language');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) =>
|
||||
knex.schema.table('user_account', (table) => {
|
||||
table.dropColumn('language');
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.table('action', (table) => {
|
||||
/* Indexes */
|
||||
|
||||
table.index('type');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) =>
|
||||
knex.schema.table('action', (table) => {
|
||||
table.dropIndex('type');
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.table('user_account', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.timestamp('password_changed_at', true);
|
||||
});
|
||||
|
||||
module.exports.down = (knex) =>
|
||||
knex.schema.table('user_account', (table) => {
|
||||
table.dropColumn('password_changed_at');
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
module.exports.up = async (knex) => {
|
||||
await knex.schema.table('board_membership', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.text('role').notNullable().defaultTo('editor');
|
||||
table.boolean('can_comment');
|
||||
});
|
||||
|
||||
return knex.schema.alterTable('board_membership', (table) => {
|
||||
table.text('role').notNullable().alter();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.down = (knex) =>
|
||||
knex.schema.table('board_membership', (table) => {
|
||||
table.dropColumn('role');
|
||||
table.dropColumn('can_comment');
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.createTable('session', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.text('access_token').notNullable();
|
||||
table.text('remote_address').notNullable();
|
||||
table.text('user_agent');
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
table.timestamp('deleted_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('user_id');
|
||||
table.unique('access_token');
|
||||
table.index('remote_address');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) => knex.schema.dropTable('session');
|
||||
@@ -1,4 +0,0 @@
|
||||
/* Move to new naming by feature */
|
||||
|
||||
module.exports.up = () => Promise.resolve();
|
||||
module.exports.down = () => Promise.resolve();
|
||||
@@ -1,118 +0,0 @@
|
||||
const path = require('path');
|
||||
const rimraf = require('rimraf');
|
||||
const sharp = require('sharp');
|
||||
|
||||
const getConfig = require('../../get-config');
|
||||
|
||||
const migrateImage = async (knex, tableName, fieldName, prevFieldName) => {
|
||||
await knex.schema.table(tableName, (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.jsonb(fieldName);
|
||||
});
|
||||
|
||||
await knex(tableName)
|
||||
.update({
|
||||
[fieldName]: knex.raw('format(\'{"dirname":"%s","extension":"jpg"}\', ??)::jsonb', [
|
||||
prevFieldName,
|
||||
]),
|
||||
})
|
||||
.whereNotNull(prevFieldName);
|
||||
|
||||
await knex.schema.table(tableName, (table) => {
|
||||
table.dropColumn(prevFieldName);
|
||||
});
|
||||
};
|
||||
|
||||
const rollbackImage = async (knex, tableName, fieldName, prevFieldName) => {
|
||||
await knex.schema.table(tableName, (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.text(prevFieldName);
|
||||
});
|
||||
|
||||
await knex(tableName)
|
||||
.update({
|
||||
[prevFieldName]: knex.raw("??->>'dirname'", [fieldName]),
|
||||
})
|
||||
.whereNotNull(fieldName);
|
||||
|
||||
await knex.schema.table(tableName, (table) => {
|
||||
table.dropColumn(fieldName);
|
||||
});
|
||||
};
|
||||
|
||||
const processAttachmentImage = async (attachment, attachmentsPath) => {
|
||||
const rootPath = path.join(attachmentsPath, attachment.dirname);
|
||||
const thumbnailsPath = path.join(rootPath, 'thumbnails');
|
||||
|
||||
const image = sharp(path.join(rootPath, attachment.filename), {
|
||||
animated: true,
|
||||
});
|
||||
|
||||
let metadata;
|
||||
try {
|
||||
metadata = await image.metadata();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { width, pageHeight: height = metadata.height } = metadata;
|
||||
const thumbnailsExtension = metadata.format === 'jpeg' ? 'jpg' : metadata.format;
|
||||
|
||||
try {
|
||||
await image
|
||||
.resize(256, height > width ? 320 : undefined, {
|
||||
kernel: sharp.kernel.nearest,
|
||||
})
|
||||
.toFile(path.join(thumbnailsPath, `cover-256.${thumbnailsExtension}`));
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (thumbnailsExtension !== 'jpg') {
|
||||
try {
|
||||
rimraf.sync(path.join(thumbnailsPath, 'cover-256.jpg'));
|
||||
} catch (error) {
|
||||
console.warn(error.stack); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
thumbnailsExtension,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.up = async (knex) => {
|
||||
await migrateImage(knex, 'user_account', 'avatar', 'avatar_dirname');
|
||||
await migrateImage(knex, 'project', 'background_image', 'background_image_dirname');
|
||||
|
||||
const config = await getConfig();
|
||||
const attachments = await knex('attachment').whereNotNull('image');
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const attachment of attachments) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const image = await processAttachmentImage(attachment, config.custom.attachmentsPath);
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await knex('attachment')
|
||||
.update({
|
||||
image: image || knex.raw('?? || \'{"thumbnailsExtension":"jpg"}\'', ['image']),
|
||||
})
|
||||
.where('id', attachment.id);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.down = async (knex) => {
|
||||
await rollbackImage(knex, 'user_account', 'avatar', 'avatar_dirname');
|
||||
await rollbackImage(knex, 'project', 'background_image', 'background_image_dirname');
|
||||
|
||||
return knex('attachment')
|
||||
.update({
|
||||
image: knex.raw("?? - 'thumbnailsExtension'", ['image']),
|
||||
})
|
||||
.whereNotNull('image');
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
module.exports.up = async (knex) => {
|
||||
await knex.schema.table('board', (table) => {
|
||||
table.dropColumn('type');
|
||||
});
|
||||
|
||||
return knex.schema.table('card', (table) => {
|
||||
table.dropNullable('list_id');
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.down = async (knex) => {
|
||||
await knex.schema.table('board', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.text('type').notNullable().defaultTo('kanban'); // FIXME: drop default
|
||||
});
|
||||
|
||||
return knex.schema.table('card', (table) => {
|
||||
table.setNullable('list_id');
|
||||
});
|
||||
};
|
||||
@@ -1,138 +0,0 @@
|
||||
const path = require('path');
|
||||
const sharp = require('sharp');
|
||||
|
||||
const getConfig = require('../../get-config');
|
||||
|
||||
const processUserAvatar = async (user, userAvatarsPath) => {
|
||||
const rootPath = path.join(userAvatarsPath, user.avatar.dirname);
|
||||
|
||||
let image = sharp(path.join(rootPath, `original.${user.avatar.extension}`), {
|
||||
animated: true,
|
||||
});
|
||||
|
||||
let metadata;
|
||||
try {
|
||||
metadata = await image.metadata();
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { width, pageHeight: height = metadata.height } = metadata;
|
||||
if (metadata.orientation && metadata.orientation > 4) {
|
||||
[image, width, height] = [image.rotate(), height, width];
|
||||
}
|
||||
|
||||
try {
|
||||
await image
|
||||
.resize(
|
||||
100,
|
||||
100,
|
||||
width < 100 || height < 100
|
||||
? {
|
||||
kernel: sharp.kernel.nearest,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
.toFile(path.join(rootPath, `square-100.${user.avatar.extension}`));
|
||||
} catch (error) {} // eslint-disable-line no-empty
|
||||
};
|
||||
|
||||
const processProjectBackgroundImage = async (project, projectBackgroundImagesPath) => {
|
||||
const rootPath = path.join(projectBackgroundImagesPath, project.background_image.dirname);
|
||||
|
||||
let image = sharp(path.join(rootPath, `original.${project.background_image.extension}`), {
|
||||
animated: true,
|
||||
});
|
||||
|
||||
let metadata;
|
||||
try {
|
||||
metadata = await image.metadata();
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { width, pageHeight: height = metadata.height } = metadata;
|
||||
if (metadata.orientation && metadata.orientation > 4) {
|
||||
[image, width, height] = [image.rotate(), height, width];
|
||||
}
|
||||
|
||||
try {
|
||||
await image
|
||||
.resize(
|
||||
336,
|
||||
200,
|
||||
width < 336 || height < 200
|
||||
? {
|
||||
kernel: sharp.kernel.nearest,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
.toFile(path.join(rootPath, `cover-336.${project.background_image.extension}`));
|
||||
} catch (error) {} // eslint-disable-line no-empty
|
||||
};
|
||||
|
||||
const processAttachmentImage = async (attachment, attachmentsPath) => {
|
||||
const rootPath = path.join(attachmentsPath, attachment.dirname);
|
||||
const thumbnailsPath = path.join(rootPath, 'thumbnails');
|
||||
|
||||
let image = sharp(path.join(rootPath, attachment.filename), {
|
||||
animated: true,
|
||||
});
|
||||
|
||||
let metadata;
|
||||
try {
|
||||
metadata = await image.metadata();
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { width, pageHeight: height = metadata.height } = metadata;
|
||||
if (metadata.orientation && metadata.orientation > 4) {
|
||||
[image, width, height] = [image.rotate(), height, width];
|
||||
}
|
||||
|
||||
const isPortrait = height > width;
|
||||
|
||||
try {
|
||||
await image
|
||||
.resize(
|
||||
256,
|
||||
isPortrait ? 320 : undefined,
|
||||
width < 256 || (isPortrait && height < 320)
|
||||
? {
|
||||
kernel: sharp.kernel.nearest,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
.toFile(path.join(thumbnailsPath, `cover-256.${attachment.image.thumbnailsExtension}`));
|
||||
} catch (error) {} // eslint-disable-line no-empty
|
||||
};
|
||||
|
||||
module.exports.up = async (knex) => {
|
||||
const config = await getConfig();
|
||||
const users = await knex('user_account').whereNotNull('avatar');
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const user of users) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await processUserAvatar(user, config.custom.userAvatarsPath);
|
||||
}
|
||||
|
||||
const projects = await knex('project').whereNotNull('background_image');
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const project of projects) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await processProjectBackgroundImage(project, config.custom.projectBackgroundImagesPath);
|
||||
}
|
||||
|
||||
const attachments = await knex('attachment').whereNotNull('image');
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const attachment of attachments) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await processAttachmentImage(attachment, config.custom.attachmentsPath);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.down = () => Promise.resolve();
|
||||
@@ -1,5 +0,0 @@
|
||||
const { addPosition, removePosition } = require('../../utils/migrations');
|
||||
|
||||
module.exports.up = (knex) => addPosition(knex, 'label', 'board_id');
|
||||
|
||||
module.exports.down = (knex) => removePosition(knex, 'label');
|
||||
@@ -1,9 +0,0 @@
|
||||
module.exports.up = (knex) =>
|
||||
knex.schema.table('card', (table) => {
|
||||
table.renameColumn('timer', 'stopwatch');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) =>
|
||||
knex.schema.table('card', (table) => {
|
||||
table.renameColumn('stopwatch', 'timer');
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
module.exports.up = async (knex) => {
|
||||
await knex.schema.createTable('identity_provider_user', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.text('issuer').notNullable();
|
||||
table.text('sub').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['issuer', 'sub']);
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
await knex.schema.table('user_account', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.boolean('is_sso').notNullable().default(false);
|
||||
|
||||
/* Modifications */
|
||||
|
||||
table.setNullable('password');
|
||||
});
|
||||
|
||||
return knex.schema.alterTable('user_account', (table) => {
|
||||
table.boolean('is_sso').notNullable().alter();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.down = async (knex) => {
|
||||
await knex.schema.dropTable('identity_provider_user');
|
||||
|
||||
return knex.schema.table('user_account', (table) => {
|
||||
table.dropColumn('is_sso');
|
||||
|
||||
table.dropNullable('password');
|
||||
});
|
||||
};
|
||||
@@ -1,68 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
const LANGUAGES = [
|
||||
'bg-BG',
|
||||
'cs-CZ',
|
||||
'da-DK',
|
||||
'de-DE',
|
||||
'en-US',
|
||||
'es-ES',
|
||||
'fa-IR',
|
||||
'fr-FR',
|
||||
'hu-HU',
|
||||
'id-ID',
|
||||
'it-IT',
|
||||
'ja-JP',
|
||||
'ko-KR',
|
||||
'nl-NL',
|
||||
'pl-PL',
|
||||
'pt-BR',
|
||||
'ro-RO',
|
||||
'ru-RU',
|
||||
'sk-SK',
|
||||
'sv-SE',
|
||||
'tr-TR',
|
||||
'uz-UZ',
|
||||
'zh-CN',
|
||||
];
|
||||
|
||||
const LANGUAGE_BY_PREV_LANGUAGE = LANGUAGES.reduce(
|
||||
(result, language) => ({
|
||||
...result,
|
||||
[language.split('-')[0]]: language,
|
||||
}),
|
||||
{},
|
||||
);
|
||||
LANGUAGE_BY_PREV_LANGUAGE.ua = 'uk-UA';
|
||||
|
||||
const PREV_LANGUAGE_BY_LANGUAGE = _.invert(LANGUAGE_BY_PREV_LANGUAGE);
|
||||
|
||||
module.exports.up = async (knex) => {
|
||||
const users = await knex('user_account').whereNotNull('language');
|
||||
const prevLanguages = [...new Set(users.map((user) => user.language))];
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const prevLanguage of prevLanguages) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await knex('user_account')
|
||||
.update({
|
||||
language: LANGUAGE_BY_PREV_LANGUAGE[prevLanguage],
|
||||
})
|
||||
.where('language', prevLanguage);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.down = async (knex) => {
|
||||
const users = await knex('user_account').whereNotNull('language');
|
||||
const languages = [...new Set(users.map((user) => user.language))];
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const language of languages) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await knex('user_account')
|
||||
.update({
|
||||
language: PREV_LANGUAGE_BY_LANGUAGE[language],
|
||||
})
|
||||
.where('language', language);
|
||||
}
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
module.exports.up = async (knex) => {
|
||||
await knex.schema.table('card', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.boolean('is_due_date_completed');
|
||||
});
|
||||
|
||||
return knex('card')
|
||||
.update({
|
||||
isDueDateCompleted: false,
|
||||
})
|
||||
.whereNotNull('due_date');
|
||||
};
|
||||
|
||||
module.exports.down = (knex) =>
|
||||
knex.schema.table('card', (table) => {
|
||||
table.dropColumn('is_due_date_completed');
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports.up = async (knex) =>
|
||||
knex.schema.table('session', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.text('http_only_token');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) =>
|
||||
knex.schema.table('session', (table) => {
|
||||
table.dropColumn('http_only_token');
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = (knex) =>
|
||||
knex.schema.alterTable('list', (table) => {
|
||||
table.text('color');
|
||||
});
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = (knex) =>
|
||||
knex.schema.alterTable('list', (table) => {
|
||||
table.dropColumn('color');
|
||||
});
|
||||
661
server/db/migrations/20250228000022_version_2.js
Normal file
661
server/db/migrations/20250228000022_version_2.js
Normal file
@@ -0,0 +1,661 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
module.exports.up = async (knex) => {
|
||||
await knex.raw(`
|
||||
CREATE EXTENSION pg_trgm;
|
||||
|
||||
CREATE SEQUENCE next_id_seq;
|
||||
CREATE FUNCTION next_id(OUT id BIGINT) AS $$
|
||||
DECLARE
|
||||
shard INT := 1;
|
||||
epoch BIGINT := 1567191600000;
|
||||
sequence BIGINT;
|
||||
milliseconds BIGINT;
|
||||
BEGIN
|
||||
SELECT nextval('next_id_seq') % 1024 INTO sequence;
|
||||
SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) INTO milliseconds;
|
||||
id := (milliseconds - epoch) << 23;
|
||||
id := id | (shard << 10);
|
||||
id := id | (sequence);
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
`);
|
||||
|
||||
await knex.schema.createTable('file_reference', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.integer('total');
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('total');
|
||||
});
|
||||
|
||||
await knex.schema
|
||||
.createTable('user_account', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.text('email').notNullable();
|
||||
table.text('password');
|
||||
table.text('role').notNullable();
|
||||
table.text('name').notNullable();
|
||||
table.text('username');
|
||||
table.jsonb('avatar');
|
||||
table.text('phone');
|
||||
table.text('organization');
|
||||
table.text('language');
|
||||
table.boolean('subscribe_to_own_cards').notNullable();
|
||||
table.boolean('subscribe_to_card_when_commenting').notNullable();
|
||||
table.boolean('turn_off_recent_card_highlighting').notNullable();
|
||||
table.boolean('enable_favorites_by_default').notNullable();
|
||||
table.text('default_editor_mode').notNullable();
|
||||
table.text('default_home_view').notNullable();
|
||||
table.text('default_projects_order').notNullable();
|
||||
table.boolean('is_sso_user').notNullable();
|
||||
table.boolean('is_deactivated').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
table.timestamp('password_changed_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique('email');
|
||||
table.index('role');
|
||||
table.index('username');
|
||||
table.index('is_deactivated');
|
||||
})
|
||||
.raw(
|
||||
'ALTER TABLE user_account ADD CONSTRAINT user_account_username_unique EXCLUDE (username WITH =) WHERE (username IS NOT NULL);',
|
||||
);
|
||||
|
||||
await knex.schema.createTable('identity_provider_user', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.text('issuer').notNullable();
|
||||
table.text('sub').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['issuer', 'sub']);
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('session', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.text('access_token').notNullable();
|
||||
table.text('http_only_token');
|
||||
table.text('remote_address').notNullable();
|
||||
table.text('user_agent');
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
table.timestamp('deleted_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('user_id');
|
||||
table.unique('access_token');
|
||||
table.index('remote_address');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('project', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('owner_project_manager_id');
|
||||
table.bigInteger('background_image_id');
|
||||
|
||||
table.text('name').notNullable();
|
||||
table.text('description');
|
||||
table.text('background_type');
|
||||
table.text('background_gradient');
|
||||
table.boolean('is_hidden').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('owner_project_manager_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('project_favorite', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('project_id').notNullable();
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['project_id', 'user_id']);
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('project_manager', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('project_id').notNullable();
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['project_id', 'user_id']);
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('background_image', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('project_id').notNullable();
|
||||
|
||||
table.text('dirname').notNullable();
|
||||
table.text('extension').notNullable();
|
||||
table.bigInteger('size_in_bytes').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('project_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('base_custom_field_group', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('project_id').notNullable();
|
||||
|
||||
table.text('name').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('project_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('board', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('project_id').notNullable();
|
||||
|
||||
table.specificType('position', 'double precision').notNullable();
|
||||
table.text('name').notNullable();
|
||||
table.text('default_view').notNullable();
|
||||
table.text('default_card_type').notNullable();
|
||||
table.boolean('limit_card_types_to_default_one').notNullable();
|
||||
table.boolean('always_display_card_creator').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('project_id');
|
||||
table.index('position');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('board_subscription', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('board_id').notNullable();
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['board_id', 'user_id']);
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('board_membership', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('project_id').notNullable();
|
||||
table.bigInteger('board_id').notNullable();
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.text('role').notNullable();
|
||||
table.boolean('can_comment');
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('project_id');
|
||||
table.unique(['board_id', 'user_id']);
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('label', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('board_id').notNullable();
|
||||
|
||||
table.specificType('position', 'double precision').notNullable();
|
||||
table.text('name');
|
||||
table.text('color').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('board_id');
|
||||
table.index('position');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('list', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('board_id').notNullable();
|
||||
|
||||
table.text('type').notNullable();
|
||||
table.specificType('position', 'double precision');
|
||||
table.text('name');
|
||||
table.text('color');
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('board_id');
|
||||
table.index('type');
|
||||
table.index('position');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('card', async (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('board_id').notNullable();
|
||||
table.bigInteger('list_id').notNullable();
|
||||
table.bigInteger('creator_user_id');
|
||||
table.bigInteger('prev_list_id');
|
||||
table.bigInteger('cover_attachment_id');
|
||||
|
||||
table.text('type').notNullable();
|
||||
table.specificType('position', 'double precision');
|
||||
table.text('name').notNullable();
|
||||
table.text('description');
|
||||
table.timestamp('due_date', true);
|
||||
table.jsonb('stopwatch');
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
table.timestamp('list_changed_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('board_id');
|
||||
table.index('list_id');
|
||||
table.index('creator_user_id');
|
||||
table.index('position');
|
||||
table.index('list_changed_at');
|
||||
}).raw(`
|
||||
CREATE INDEX card_name_index ON card USING GIN (name gin_trgm_ops);
|
||||
CREATE INDEX card_description_index ON card USING GIN (description gin_trgm_ops);
|
||||
`);
|
||||
|
||||
await knex.schema.createTable('card_subscription', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.boolean('is_permanent').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['card_id', 'user_id']);
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('card_membership', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
table.bigInteger('user_id').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['card_id', 'user_id']);
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('card_label', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
table.bigInteger('label_id').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['card_id', 'label_id']);
|
||||
table.index('label_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('task_list', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
|
||||
table.specificType('position', 'double precision').notNullable();
|
||||
table.text('name').notNullable();
|
||||
table.boolean('show_on_front_of_card').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('card_id');
|
||||
table.index('position');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('task', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('task_list_id').notNullable();
|
||||
table.bigInteger('assignee_user_id');
|
||||
|
||||
table.specificType('position', 'double precision').notNullable();
|
||||
table.text('name').notNullable();
|
||||
table.boolean('is_completed').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('task_list_id');
|
||||
table.index('assignee_user_id');
|
||||
table.index('position');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('attachment', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
table.bigInteger('creator_user_id');
|
||||
|
||||
table.text('type').notNullable();
|
||||
table.jsonb('data').notNullable();
|
||||
table.text('name').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('card_id');
|
||||
table.index('creator_user_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('custom_field_group', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('board_id');
|
||||
table.bigInteger('card_id');
|
||||
table.bigInteger('base_custom_field_group_id');
|
||||
|
||||
table.specificType('position', 'double precision').notNullable();
|
||||
table.text('name');
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('board_id');
|
||||
table.index('card_id');
|
||||
table.index('base_custom_field_group_id');
|
||||
table.index('position');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('custom_field', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('base_custom_field_group_id');
|
||||
table.bigInteger('custom_field_group_id');
|
||||
|
||||
table.specificType('position', 'double precision').notNullable();
|
||||
table.text('name').notNullable();
|
||||
table.boolean('show_on_front_of_card').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('base_custom_field_group_id');
|
||||
table.index('custom_field_group_id');
|
||||
table.index('position');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('custom_field_value', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
table.bigInteger('custom_field_group_id').notNullable();
|
||||
table.bigInteger('custom_field_id').notNullable();
|
||||
|
||||
table.text('content').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.unique(['card_id', 'custom_field_group_id', 'custom_field_id']);
|
||||
table.index('custom_field_group_id');
|
||||
table.index('custom_field_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('comment', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
table.bigInteger('user_id');
|
||||
|
||||
table.text('text').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('card_id');
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('action', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('card_id').notNullable();
|
||||
table.bigInteger('user_id');
|
||||
|
||||
table.text('type').notNullable();
|
||||
table.jsonb('data').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('card_id');
|
||||
table.index('user_id');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('notification', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('user_id').notNullable();
|
||||
table.bigInteger('creator_user_id');
|
||||
table.bigInteger('board_id').notNullable();
|
||||
table.bigInteger('card_id').notNullable();
|
||||
table.bigInteger('comment_id');
|
||||
table.bigInteger('action_id');
|
||||
|
||||
table.text('type').notNullable();
|
||||
table.jsonb('data').notNullable();
|
||||
table.boolean('is_read').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('user_id');
|
||||
table.index('creator_user_id');
|
||||
table.index('card_id');
|
||||
table.index('comment_id');
|
||||
table.index('action_id');
|
||||
table.index('is_read');
|
||||
});
|
||||
|
||||
return knex.schema.createTable('notification_service', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||
|
||||
table.bigInteger('user_id');
|
||||
table.bigInteger('board_id');
|
||||
|
||||
table.text('url').notNullable();
|
||||
table.text('format').notNullable();
|
||||
|
||||
table.timestamp('created_at', true);
|
||||
table.timestamp('updated_at', true);
|
||||
|
||||
/* Indexes */
|
||||
|
||||
table.index('user_id');
|
||||
table.index('board_id');
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.down = async (knex) => {
|
||||
await knex.schema.dropTable('file_reference');
|
||||
await knex.schema.dropTable('user_account');
|
||||
await knex.schema.dropTable('identity_provider_user');
|
||||
await knex.schema.dropTable('session');
|
||||
await knex.schema.dropTable('project');
|
||||
await knex.schema.dropTable('project_favorite');
|
||||
await knex.schema.dropTable('project_manager');
|
||||
await knex.schema.dropTable('background_image');
|
||||
await knex.schema.dropTable('base_custom_field_group');
|
||||
await knex.schema.dropTable('board');
|
||||
await knex.schema.dropTable('board_subscription');
|
||||
await knex.schema.dropTable('board_membership');
|
||||
await knex.schema.dropTable('label');
|
||||
await knex.schema.dropTable('list');
|
||||
await knex.schema.dropTable('card');
|
||||
await knex.schema.dropTable('card_subscription');
|
||||
await knex.schema.dropTable('card_membership');
|
||||
await knex.schema.dropTable('card_label');
|
||||
await knex.schema.dropTable('task_list');
|
||||
await knex.schema.dropTable('task');
|
||||
await knex.schema.dropTable('attachment');
|
||||
await knex.schema.dropTable('custom_field_group');
|
||||
await knex.schema.dropTable('custom_field');
|
||||
await knex.schema.dropTable('custom_field_value');
|
||||
await knex.schema.dropTable('comment');
|
||||
await knex.schema.dropTable('action');
|
||||
await knex.schema.dropTable('notification');
|
||||
await knex.schema.dropTable('notification_service');
|
||||
|
||||
return knex.raw(`
|
||||
DROP EXTENSION pg_trgm;
|
||||
|
||||
DROP SEQUENCE next_id_seq;
|
||||
DROP FUNCTION next_id(OUT id BIGINT);
|
||||
`);
|
||||
};
|
||||
0
server/db/seeds/.gitkeep
Normal file
0
server/db/seeds/.gitkeep
Normal file
@@ -1,9 +1,15 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
const buildData = () => {
|
||||
const data = {
|
||||
isAdmin: true,
|
||||
isSso: false,
|
||||
role: 'admin',
|
||||
isSsoUser: false,
|
||||
isDeactivated: false,
|
||||
};
|
||||
|
||||
if (process.env.DEFAULT_ADMIN_PASSWORD) {
|
||||
@@ -20,21 +26,66 @@ const buildData = () => {
|
||||
};
|
||||
|
||||
exports.seed = async (knex) => {
|
||||
if (!process.env.DEFAULT_ADMIN_EMAIL) {
|
||||
return;
|
||||
const email = process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase();
|
||||
|
||||
if (email) {
|
||||
const data = buildData();
|
||||
|
||||
let userId;
|
||||
try {
|
||||
[{ id: userId }] = await knex('user_account').insert(
|
||||
{
|
||||
...data,
|
||||
email,
|
||||
subscribeToOwnCards: false,
|
||||
subscribeToCardWhenCommenting: true,
|
||||
turnOffRecentCardHighlighting: false,
|
||||
enableFavoritesByDefault: false,
|
||||
defaultEditorMode: 'wysiwyg',
|
||||
defaultHomeView: 'groupedProjects',
|
||||
defaultProjectsOrder: 'byDefault',
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
'id',
|
||||
);
|
||||
} catch (error) {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
await knex('user_account').update(data).where('email', email);
|
||||
}
|
||||
}
|
||||
|
||||
const email = process.env.DEFAULT_ADMIN_EMAIL.toLowerCase();
|
||||
const data = buildData();
|
||||
const activeUsersLimit = parseInt(process.env.ACTIVE_USERS_LIMIT, 10);
|
||||
|
||||
try {
|
||||
await knex('user_account').insert({
|
||||
...data,
|
||||
email,
|
||||
subscribeToOwnCards: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
} catch (error) {
|
||||
await knex('user_account').update(data).where('email', email);
|
||||
if (!Number.isNaN(activeUsersLimit)) {
|
||||
let orderByQuery;
|
||||
let orderByQueryValues;
|
||||
|
||||
if (email) {
|
||||
orderByQuery = 'CASE WHEN email = ? THEN 0 WHEN role = ? THEN 1 ELSE 2 END';
|
||||
orderByQueryValues = [email, 'admin'];
|
||||
} else {
|
||||
orderByQuery = 'CASE WHEN role = ? THEN 0 ELSE 1 END';
|
||||
orderByQueryValues = 'admin';
|
||||
}
|
||||
|
||||
const users = await knex('user_account')
|
||||
.select('id')
|
||||
.where('is_deactivated', false)
|
||||
.orderByRaw(orderByQuery, orderByQueryValues)
|
||||
.orderBy('id')
|
||||
.offset(activeUsersLimit);
|
||||
|
||||
if (users.length > 0) {
|
||||
const userIds = users.map(({ id }) => id);
|
||||
|
||||
await knex('user_account')
|
||||
.update({
|
||||
isDeactivated: true,
|
||||
})
|
||||
.whereIn('id', userIds);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
859
server/db/upgrade.js
Normal file
859
server/db/upgrade.js
Normal file
@@ -0,0 +1,859 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
const { getEncoding } = require('istextorbinary');
|
||||
const mime = require('mime');
|
||||
const uuid = require('uuid');
|
||||
const sharp = require('sharp');
|
||||
const initKnex = require('knex');
|
||||
const sails = require('sails');
|
||||
const rc = require('sails/accessible/rc');
|
||||
const _ = require('lodash');
|
||||
|
||||
const knexfile = require('./knexfile');
|
||||
const { MAX_SIZE_IN_BYTES_TO_GET_ENCODING, POSITION_GAP } = require('../constants');
|
||||
|
||||
const PrevActionTypes = {
|
||||
COMMENT_CARD: 'commentCard',
|
||||
};
|
||||
|
||||
const PROJECT_BACKGROUND_IMAGES_PATH_SEGMENT = 'public/project-background-images';
|
||||
|
||||
const readStreamToBuffer = (readStream) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
|
||||
readStream.on('data', (chunk) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
readStream.on('end', () => {
|
||||
resolve(Buffer.concat(chunks));
|
||||
});
|
||||
|
||||
readStream.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
const loadSails = () =>
|
||||
new Promise((resolve) => {
|
||||
sails.load(
|
||||
{
|
||||
...rc('sails'),
|
||||
log: {
|
||||
level: 'error',
|
||||
},
|
||||
},
|
||||
resolve,
|
||||
);
|
||||
});
|
||||
|
||||
const knex = initKnex(knexfile);
|
||||
|
||||
const upgradeDatabase = async () => {
|
||||
await knex.transaction(async (trx) => {
|
||||
await trx.raw(`
|
||||
CREATE SCHEMA v1;
|
||||
|
||||
ALTER SEQUENCE next_id_seq SET SCHEMA v1;
|
||||
ALTER FUNCTION next_id(OUT id BIGINT) SET SCHEMA v1;
|
||||
|
||||
ALTER TABLE migration SET SCHEMA v1;
|
||||
ALTER TABLE migration_lock SET SCHEMA v1;
|
||||
ALTER TABLE archive SET SCHEMA v1;
|
||||
ALTER TABLE user_account SET SCHEMA v1;
|
||||
ALTER TABLE identity_provider_user SET SCHEMA v1;
|
||||
ALTER TABLE session SET SCHEMA v1;
|
||||
ALTER TABLE project SET SCHEMA v1;
|
||||
ALTER TABLE project_manager SET SCHEMA v1;
|
||||
ALTER TABLE board SET SCHEMA v1;
|
||||
ALTER TABLE board_membership SET SCHEMA v1;
|
||||
ALTER TABLE label SET SCHEMA v1;
|
||||
ALTER TABLE list SET SCHEMA v1;
|
||||
ALTER TABLE card SET SCHEMA v1;
|
||||
ALTER TABLE card_subscription SET SCHEMA v1;
|
||||
ALTER TABLE card_membership SET SCHEMA v1;
|
||||
ALTER TABLE card_label SET SCHEMA v1;
|
||||
ALTER TABLE task SET SCHEMA v1;
|
||||
ALTER TABLE attachment SET SCHEMA v1;
|
||||
ALTER TABLE action SET SCHEMA v1;
|
||||
ALTER TABLE notification SET SCHEMA v1;
|
||||
`);
|
||||
|
||||
await trx.migrate.up();
|
||||
|
||||
const users = await trx('user_account').withSchema('v1').whereNull('deleted_at');
|
||||
|
||||
const userIds = users.map(({ id }) => id);
|
||||
const userIdsSet = new Set(userIds);
|
||||
const whereInUserIds = ['0', ...userIds];
|
||||
|
||||
if (users.length > 0) {
|
||||
await trx('user_account').insert(
|
||||
users.map((user) => ({
|
||||
..._.pick(user, [
|
||||
'id',
|
||||
'email',
|
||||
'password',
|
||||
'name',
|
||||
'username',
|
||||
'phone',
|
||||
'organization',
|
||||
'language',
|
||||
'subscribe_to_own_cards',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'password_changed_at',
|
||||
]),
|
||||
role: user.is_admin ? User.Roles.ADMIN : User.Roles.BOARD_USER,
|
||||
avatar: user.avatar && {
|
||||
...user.avatar,
|
||||
sizeInBytes: 0,
|
||||
},
|
||||
subscribe_to_card_when_commenting: true,
|
||||
turn_off_recent_card_highlighting: false,
|
||||
enable_favorites_by_default: false,
|
||||
default_editor_mode: User.EditorModes.WYSIWYG,
|
||||
default_home_view: User.HomeViews.GROUPED_PROJECTS,
|
||||
default_projects_order: User.ProjectOrders.BY_DEFAULT,
|
||||
is_sso_user: user.is_sso,
|
||||
is_deactivated: false,
|
||||
})),
|
||||
);
|
||||
|
||||
const identityProviderUsers = await trx('identity_provider_user')
|
||||
.withSchema('v1')
|
||||
.whereIn('user_id', whereInUserIds);
|
||||
|
||||
if (identityProviderUsers.length > 0) {
|
||||
await trx('identity_provider_user').insert(identityProviderUsers);
|
||||
}
|
||||
|
||||
const sessions = await trx('session').withSchema('v1').whereIn('user_id', whereInUserIds);
|
||||
|
||||
if (sessions.length > 0) {
|
||||
await trx('session').insert(sessions);
|
||||
}
|
||||
}
|
||||
|
||||
const projects = await trx('project').withSchema('v1');
|
||||
const whereInProjectIds = ['0', ...projects.map(({ id }) => id)];
|
||||
|
||||
if (projects.length > 0) {
|
||||
const projectsWithBackgroundImage = projects.filter((project) => project.background_image);
|
||||
|
||||
const backgroundImageIdByProjectId = {};
|
||||
if (projectsWithBackgroundImage.length > 0) {
|
||||
const createdAt = new Date().toISOString();
|
||||
|
||||
const backgroundImages = await trx('background_image').insert(
|
||||
projectsWithBackgroundImage.map((project) => ({
|
||||
...project.background_image,
|
||||
project_id: project.id,
|
||||
size_in_bytes: 0,
|
||||
created_at: createdAt,
|
||||
})),
|
||||
['id', 'project_id'],
|
||||
);
|
||||
|
||||
backgroundImages.forEach((backgroundImage) => {
|
||||
backgroundImageIdByProjectId[backgroundImage.project_id] = backgroundImage.id;
|
||||
});
|
||||
}
|
||||
|
||||
await trx('project').insert(
|
||||
projects.map((project) => {
|
||||
const data = {
|
||||
..._.pick(project, ['id', 'name', 'created_at', 'updated_at']),
|
||||
is_hidden: false,
|
||||
};
|
||||
|
||||
if (project.background) {
|
||||
data.background_type = project.background.type;
|
||||
|
||||
switch (project.background.type) {
|
||||
case Project.BackgroundTypes.GRADIENT:
|
||||
data.background_gradient = project.background.name;
|
||||
|
||||
break;
|
||||
case Project.BackgroundTypes.IMAGE:
|
||||
data.background_image_id = backgroundImageIdByProjectId[project.id];
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const projectManagers = await trx('project_manager')
|
||||
.withSchema('v1')
|
||||
.whereIn('project_id', whereInProjectIds);
|
||||
|
||||
if (projectManagers.length > 0) {
|
||||
await trx('project_manager').insert(projectManagers);
|
||||
}
|
||||
|
||||
const boards = await trx('board').withSchema('v1').whereIn('project_id', whereInProjectIds);
|
||||
|
||||
const projectIdByBoardId = boards.reduce(
|
||||
(result, board) => ({
|
||||
...result,
|
||||
[board.id]: board.project_id,
|
||||
}),
|
||||
{},
|
||||
);
|
||||
|
||||
const whereInBoardIds = ['0', ...Object.keys(projectIdByBoardId)];
|
||||
|
||||
if (boards.length > 0) {
|
||||
await trx('board').insert(
|
||||
boards.map((board) => ({
|
||||
..._.pick(board, ['id', 'project_id', 'position', 'name', 'created_at', 'updated_at']),
|
||||
default_view: Board.Views.KANBAN,
|
||||
default_card_type: Card.Types.PROJECT,
|
||||
limit_card_types_to_default_one: false,
|
||||
always_display_card_creator: false,
|
||||
})),
|
||||
);
|
||||
|
||||
const createdAt = new Date().toISOString();
|
||||
|
||||
await trx('list').insert(
|
||||
boards.flatMap((board) =>
|
||||
[List.Types.ARCHIVE, List.Types.TRASH].map((type) => ({
|
||||
type,
|
||||
board_id: board.id,
|
||||
created_at: createdAt,
|
||||
})),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const boardMemberships = await trx('board_membership')
|
||||
.withSchema('v1')
|
||||
.whereIn('board_id', whereInBoardIds);
|
||||
|
||||
if (boardMemberships.length > 0) {
|
||||
await trx('board_membership').insert(
|
||||
boardMemberships.map((boardMembership) => ({
|
||||
..._.pick(boardMembership, [
|
||||
'id',
|
||||
'board_id',
|
||||
'user_id',
|
||||
'role',
|
||||
'can_comment',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]),
|
||||
project_id: projectIdByBoardId[boardMembership.board_id],
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
const labels = await trx('label').withSchema('v1').whereIn('board_id', whereInBoardIds);
|
||||
|
||||
if (labels.length > 0) {
|
||||
await trx('label').insert(labels);
|
||||
}
|
||||
|
||||
const lists = await trx('list').withSchema('v1').whereIn('board_id', whereInBoardIds);
|
||||
const whereInListIds = ['0', ...lists.map(({ id }) => id)];
|
||||
|
||||
if (lists.length > 0) {
|
||||
await trx('list').insert(
|
||||
lists.map((list) => ({
|
||||
..._.pick(list, [
|
||||
'id',
|
||||
'board_id',
|
||||
'position',
|
||||
'name',
|
||||
'color',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]),
|
||||
type: List.Types.ACTIVE,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
const cards = await trx('card')
|
||||
.withSchema('v1')
|
||||
.whereIn('board_id', whereInBoardIds)
|
||||
.whereIn('list_id', whereInListIds);
|
||||
|
||||
const cardById = _.keyBy(cards, 'id');
|
||||
const whereInCardIds = ['0', ...Object.keys(cardById)];
|
||||
|
||||
if (cards.length > 0) {
|
||||
await trx('card').insert(
|
||||
cards.map((card) => ({
|
||||
..._.pick(card, [
|
||||
'id',
|
||||
'board_id',
|
||||
'list_id',
|
||||
'cover_attachment_id',
|
||||
'position',
|
||||
'name',
|
||||
'description',
|
||||
'due_date',
|
||||
'stopwatch',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]),
|
||||
creator_user_id: userIdsSet.has(card.creator_user_id) ? card.creator_user_id : null,
|
||||
type: Card.Types.PROJECT,
|
||||
list_changed_at: card.created_at,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
const cardSubscriptions = await trx('card_subscription')
|
||||
.withSchema('v1')
|
||||
.whereIn('card_id', whereInCardIds);
|
||||
|
||||
if (cardSubscriptions.length > 0) {
|
||||
await trx('card_subscription').insert(cardSubscriptions);
|
||||
}
|
||||
|
||||
const cardMemberships = await trx('card_membership')
|
||||
.withSchema('v1')
|
||||
.whereIn('card_id', whereInCardIds);
|
||||
|
||||
if (cardMemberships.length > 0) {
|
||||
await trx('card_membership').insert(cardMemberships);
|
||||
}
|
||||
|
||||
const cardLabels = await trx('card_label').withSchema('v1').whereIn('card_id', whereInCardIds);
|
||||
|
||||
if (cardLabels.length > 0) {
|
||||
await trx('card_label').insert(cardLabels);
|
||||
}
|
||||
|
||||
const tasks = await trx('task').withSchema('v1').whereIn('card_id', whereInCardIds);
|
||||
|
||||
const tasksByCardId = _.groupBy(tasks, 'card_id');
|
||||
const taskCardIds = Object.keys(tasksByCardId);
|
||||
|
||||
if (taskCardIds.length > 0) {
|
||||
const createdAt = new Date().toISOString();
|
||||
|
||||
const taskLists = await trx('task_list').insert(
|
||||
taskCardIds.map((cardId) => ({
|
||||
card_id: cardId,
|
||||
position: POSITION_GAP,
|
||||
name: 'Task List',
|
||||
show_on_front_of_card: true,
|
||||
created_at: createdAt,
|
||||
})),
|
||||
['id', 'card_id'],
|
||||
);
|
||||
|
||||
await trx('task').insert(
|
||||
taskLists.flatMap((taskList) =>
|
||||
tasksByCardId[taskList.card_id].map((task) => ({
|
||||
..._.pick(task, ['id', 'position', 'name', 'is_completed', 'created_at', 'updated_at']),
|
||||
task_list_id: taskList.id,
|
||||
})),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const attachments = await trx('attachment').withSchema('v1').whereIn('card_id', whereInCardIds);
|
||||
|
||||
if (attachments.length > 0) {
|
||||
await trx('attachment').insert(
|
||||
attachments.map((attachment) => ({
|
||||
..._.pick(attachment, ['id', 'card_id', 'name', 'created_at', 'updated_at']),
|
||||
creator_user_id: userIdsSet.has(attachment.creator_user_id)
|
||||
? attachment.creator_user_id
|
||||
: null,
|
||||
type: Attachment.Types.FILE,
|
||||
data: {
|
||||
fileReferenceId: attachment.dirname,
|
||||
filename: attachment.filename,
|
||||
mimeType: mime.getType(attachment.filename),
|
||||
sizeInBytes: 0,
|
||||
encoding: null,
|
||||
image: attachment.image,
|
||||
},
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
const actions = await trx('action').withSchema('v1').whereIn('card_id', whereInCardIds);
|
||||
|
||||
const actionById = _.keyBy(actions, 'id');
|
||||
const whereInActionIds = ['0', ...Object.keys(actionById)];
|
||||
|
||||
const commentActions = [];
|
||||
const otherActions = [];
|
||||
|
||||
actions.forEach((action) => {
|
||||
if (action.type === PrevActionTypes.COMMENT_CARD) {
|
||||
commentActions.push(action);
|
||||
} else {
|
||||
otherActions.push(action);
|
||||
}
|
||||
});
|
||||
|
||||
if (commentActions.length > 0) {
|
||||
await trx('comment').insert(
|
||||
commentActions.map((action) => ({
|
||||
..._.pick(action, ['id', 'card_id', 'created_at', 'updated_at']),
|
||||
user_id: userIdsSet.has(action.user_id) ? action.user_id : null,
|
||||
text: action.data.text,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
if (otherActions.length > 0) {
|
||||
await trx('action').insert(
|
||||
otherActions.map((action) => {
|
||||
const data = {
|
||||
..._.pick(action, ['id', 'card_id', 'type', 'created_at', 'updated_at']),
|
||||
user_id: userIdsSet.has(action.user_id) ? action.user_id : null,
|
||||
};
|
||||
|
||||
switch (action.type) {
|
||||
case Action.Types.CREATE_CARD:
|
||||
data.data = {
|
||||
list: {
|
||||
...action.data.list,
|
||||
type: List.Types.ACTIVE,
|
||||
},
|
||||
};
|
||||
|
||||
break;
|
||||
case Action.Types.MOVE_CARD:
|
||||
data.data = {
|
||||
fromList: {
|
||||
...action.data.fromList,
|
||||
type: List.Types.ACTIVE,
|
||||
},
|
||||
toList: {
|
||||
...action.data.toList,
|
||||
type: List.Types.ACTIVE,
|
||||
},
|
||||
};
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
return data;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const notifications = await trx('notification')
|
||||
.withSchema('v1')
|
||||
.whereIn('user_id', whereInUserIds)
|
||||
.whereIn('action_id', whereInActionIds)
|
||||
.whereIn('card_id', whereInCardIds);
|
||||
|
||||
if (notifications.length > 0) {
|
||||
await trx('notification').insert(
|
||||
notifications.map((notification) => {
|
||||
const card = cardById[notification.card_id];
|
||||
const action = actionById[notification.action_id];
|
||||
|
||||
const data = {
|
||||
..._.pick(notification, [
|
||||
'id',
|
||||
'user_id',
|
||||
'card_id',
|
||||
'is_read',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]),
|
||||
creator_user_id: userIdsSet.has(notification.creator_user_id)
|
||||
? notification.creator_user_id
|
||||
: null,
|
||||
board_id: card.board_id,
|
||||
type: action.type,
|
||||
};
|
||||
|
||||
if (action.type === PrevActionTypes.COMMENT_CARD) {
|
||||
Object.assign(data, {
|
||||
comment_id: action.id,
|
||||
data: {
|
||||
card: _.pick(card, ['name']),
|
||||
text: action.data.text,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
Object.assign(data, {
|
||||
action_id: action.id,
|
||||
data: {
|
||||
fromList: {
|
||||
...action.data.fromList,
|
||||
type: List.Types.ACTIVE,
|
||||
},
|
||||
toList: {
|
||||
...action.data.toList,
|
||||
type: List.Types.ACTIVE,
|
||||
},
|
||||
card: _.pick(card, ['name']),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await trx.schema.dropSchema('v1', true);
|
||||
});
|
||||
};
|
||||
|
||||
const upgradeUserAvatars = async () => {
|
||||
const fileManager = sails.hooks['file-manager'].getInstance();
|
||||
|
||||
const dirnames = await fileManager.listDir(sails.config.custom.userAvatarsPathSegment);
|
||||
const users = await knex('user_account').whereNotNull('avatar');
|
||||
|
||||
if (dirnames) {
|
||||
const userByDirname = _.keyBy(users, 'avatar.dirname');
|
||||
|
||||
for (const dirname of dirnames) {
|
||||
const user = userByDirname[dirname];
|
||||
const dirPathSegment = `${sails.config.custom.userAvatarsPathSegment}/${dirname}`;
|
||||
|
||||
if (user) {
|
||||
const sizeInBytes = await fileManager.getSizeInBytes(
|
||||
`${dirPathSegment}/original.${user.avatar.extension}`,
|
||||
);
|
||||
|
||||
await knex('user_account')
|
||||
.update({
|
||||
avatar: knex.raw("?? || jsonb_build_object('sizeInBytes', ?::bigint)", [
|
||||
'avatar',
|
||||
sizeInBytes,
|
||||
]),
|
||||
})
|
||||
.where('id', user.id);
|
||||
} else {
|
||||
await fileManager.deleteDir(dirPathSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const { avatar } of users) {
|
||||
const dirPathSegment = `${sails.config.custom.userAvatarsPathSegment}/${avatar.dirname}`;
|
||||
|
||||
const isExists = await fileManager.isExists(`${dirPathSegment}/cover-180.${avatar.extension}`);
|
||||
|
||||
if (isExists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await fileManager.delete(`${dirPathSegment}/square-100.${avatar.extension}`);
|
||||
|
||||
let readStream;
|
||||
try {
|
||||
readStream = await fileManager.read(`${dirPathSegment}/original.${avatar.extension}`);
|
||||
} catch (error) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const originalBuffer = await readStreamToBuffer(readStream);
|
||||
|
||||
const image = sharp(originalBuffer, {
|
||||
animated: true,
|
||||
});
|
||||
|
||||
const cover180Buffer = await image
|
||||
.resize(180, 180, {
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.png({
|
||||
quality: 75,
|
||||
force: false,
|
||||
})
|
||||
.toBuffer();
|
||||
|
||||
await fileManager.save(
|
||||
`${dirPathSegment}/cover-180.${avatar.extension}`,
|
||||
cover180Buffer,
|
||||
mime.getType(avatar.extension),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const upgradeBackgroundImages = async () => {
|
||||
const fileManager = sails.hooks['file-manager'].getInstance();
|
||||
|
||||
await fileManager.renameDir(
|
||||
PROJECT_BACKGROUND_IMAGES_PATH_SEGMENT,
|
||||
sails.config.custom.backgroundImagesPathSegment,
|
||||
);
|
||||
|
||||
const dirnames = await fileManager.listDir(sails.config.custom.backgroundImagesPathSegment);
|
||||
const backgroundImages = await knex('background_image');
|
||||
|
||||
if (dirnames) {
|
||||
const backgroundImageByDirname = _.keyBy(backgroundImages, 'dirname');
|
||||
|
||||
for (const dirname of dirnames) {
|
||||
const backgroundImage = backgroundImageByDirname[dirname];
|
||||
const dirPathSegment = `${sails.config.custom.backgroundImagesPathSegment}/${dirname}`;
|
||||
|
||||
if (backgroundImage) {
|
||||
const sizeInBytes = await fileManager.getSizeInBytes(
|
||||
`${dirPathSegment}/original.${backgroundImage.extension}`,
|
||||
);
|
||||
|
||||
await knex('background_image')
|
||||
.update({
|
||||
size_in_bytes: sizeInBytes,
|
||||
})
|
||||
.where('id', backgroundImage.id);
|
||||
} else {
|
||||
await fileManager.deleteDir(dirPathSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const backgroundImage of backgroundImages) {
|
||||
const dirPathSegment = `${sails.config.custom.backgroundImagesPathSegment}/${backgroundImage.dirname}`;
|
||||
|
||||
const isExists = await fileManager.isExists(
|
||||
`${dirPathSegment}/outside-360.${backgroundImage.extension}`,
|
||||
);
|
||||
|
||||
if (isExists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await fileManager.delete(`${dirPathSegment}/cover-336.${backgroundImage.extension}`);
|
||||
|
||||
let readStream;
|
||||
try {
|
||||
readStream = await fileManager.read(
|
||||
`${dirPathSegment}/original.${backgroundImage.extension}`,
|
||||
);
|
||||
} catch (error) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const originalBuffer = await readStreamToBuffer(readStream);
|
||||
|
||||
const image = sharp(originalBuffer, {
|
||||
animated: true,
|
||||
});
|
||||
|
||||
const outside360Buffer = await image
|
||||
.resize(360, 360, {
|
||||
fit: 'outside',
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.png({
|
||||
quality: 75,
|
||||
force: false,
|
||||
})
|
||||
.toBuffer();
|
||||
|
||||
await fileManager.save(
|
||||
`${dirPathSegment}/outside-360.${backgroundImage.extension}`,
|
||||
outside360Buffer,
|
||||
mime.getType(backgroundImage.extension),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const upgradeFileAttachments = async () => {
|
||||
const fileManager = sails.hooks['file-manager'].getInstance();
|
||||
|
||||
const dirnames = await fileManager.listDir(sails.config.custom.attachmentsPathSegment);
|
||||
const attachments = await knex('attachment').where('type', Attachment.Types.FILE);
|
||||
|
||||
const fileReferenceIds = [];
|
||||
if (dirnames) {
|
||||
const attachmentByDirname = _.keyBy(attachments, 'data.fileReferenceId');
|
||||
|
||||
for (const dirname of dirnames) {
|
||||
const attachment = attachmentByDirname[dirname];
|
||||
const dirPathSegment = `${sails.config.custom.attachmentsPathSegment}/${dirname}`;
|
||||
|
||||
if (attachment) {
|
||||
if (uuid.validate(dirname)) {
|
||||
const fileReferenceId = await knex.transaction(async (trx) => {
|
||||
const [{ id }] = await trx('file_reference').insert(
|
||||
{
|
||||
total: 1,
|
||||
created_at: new Date().toISOString(),
|
||||
},
|
||||
'id',
|
||||
);
|
||||
|
||||
const sizeInBytes = await fileManager.getSizeInBytes(
|
||||
`${dirPathSegment}/${attachment.data.filename}`,
|
||||
);
|
||||
|
||||
let encoding = null;
|
||||
if (sizeInBytes && sizeInBytes <= MAX_SIZE_IN_BYTES_TO_GET_ENCODING) {
|
||||
const readStream = await fileManager.read(
|
||||
`${dirPathSegment}/${attachment.data.filename}`,
|
||||
);
|
||||
|
||||
const buffer = await readStreamToBuffer(readStream);
|
||||
encoding = getEncoding(buffer);
|
||||
}
|
||||
|
||||
await trx('attachment')
|
||||
.update({
|
||||
data: trx.raw(
|
||||
"?? || jsonb_build_object('fileReferenceId', ?::text, 'sizeInBytes', ?::bigint, 'encoding', ?::text)",
|
||||
['data', id, sizeInBytes, encoding],
|
||||
),
|
||||
})
|
||||
.where('id', attachment.id);
|
||||
|
||||
await fileManager.renameDir(
|
||||
`${dirPathSegment}`,
|
||||
`${sails.config.custom.attachmentsPathSegment}/${id}`,
|
||||
);
|
||||
|
||||
return id;
|
||||
});
|
||||
|
||||
fileReferenceIds.push(fileReferenceId);
|
||||
} else {
|
||||
fileReferenceIds.push(dirname);
|
||||
}
|
||||
} else {
|
||||
await fileManager.deleteDir(dirPathSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fileReferenceIds.length > 0) {
|
||||
await knex('attachment')
|
||||
.update({
|
||||
data: knex.raw('?? || \'{"fileReferenceId":null}\'', 'data'),
|
||||
})
|
||||
.where('type', Attachment.Types.FILE)
|
||||
.whereRaw(`??->>'fileReferenceId' NOT IN (${fileReferenceIds.map(() => '?').join(', ')})`, [
|
||||
'data',
|
||||
...fileReferenceIds,
|
||||
]);
|
||||
}
|
||||
|
||||
const imageAttachments = await knex('attachment')
|
||||
.where('type', Attachment.Types.FILE)
|
||||
.whereRaw("??->>'image' IS NOT NULL", 'data');
|
||||
|
||||
for (const { data } of imageAttachments) {
|
||||
const dirPathSegment = `${sails.config.custom.attachmentsPathSegment}/${data.fileReferenceId}`;
|
||||
const thumbnailsPathSegment = `${dirPathSegment}/thumbnails`;
|
||||
|
||||
const isExists = await fileManager.isExists(
|
||||
`${thumbnailsPathSegment}/outside-720.${data.image.thumbnailsExtension}`,
|
||||
);
|
||||
|
||||
if (isExists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await fileManager.deleteDir(thumbnailsPathSegment);
|
||||
|
||||
let readStream;
|
||||
try {
|
||||
readStream = await fileManager.read(`${dirPathSegment}/${data.filename}`);
|
||||
} catch (error) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const buffer = await readStreamToBuffer(readStream);
|
||||
|
||||
const image = sharp(buffer, {
|
||||
animated: true,
|
||||
});
|
||||
|
||||
const outside360Buffer = await image
|
||||
.resize(360, 360, {
|
||||
fit: 'outside',
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.png({
|
||||
quality: 75,
|
||||
force: false,
|
||||
})
|
||||
.toBuffer();
|
||||
|
||||
await fileManager.save(
|
||||
`${thumbnailsPathSegment}/outside-360.${data.image.thumbnailsExtension}`,
|
||||
outside360Buffer,
|
||||
data.mimeType,
|
||||
);
|
||||
|
||||
const outside720Buffer = await image
|
||||
.resize(720, 720, {
|
||||
fit: 'outside',
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.png({
|
||||
quality: 75,
|
||||
force: false,
|
||||
})
|
||||
.toBuffer();
|
||||
|
||||
await fileManager.save(
|
||||
`${thumbnailsPathSegment}/outside-720.${data.image.thumbnailsExtension}`,
|
||||
outside720Buffer,
|
||||
data.mimeType,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
let migrations;
|
||||
try {
|
||||
migrations = await knex('migration').orderBy('id');
|
||||
} catch (error) {
|
||||
throw new Error('Nothing to upgrade');
|
||||
}
|
||||
|
||||
const migrationNames = migrations.map(({ name }) => name);
|
||||
|
||||
const isV1 = migrationNames[0] === '20180721020022_create_next_id_function.js';
|
||||
const isLatestV1 = migrationNames.at(-1) === '20250131202710_add_list_color.js';
|
||||
|
||||
if (isV1 && !isLatestV1) {
|
||||
throw new Error('Update to latest v1 first');
|
||||
}
|
||||
|
||||
await loadSails();
|
||||
|
||||
if (isV1) {
|
||||
console.log('Upgrading database...');
|
||||
await upgradeDatabase();
|
||||
}
|
||||
|
||||
console.log('Upgrading files...');
|
||||
|
||||
await upgradeUserAvatars();
|
||||
await upgradeBackgroundImages();
|
||||
await upgradeFileAttachments();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
knex.destroy();
|
||||
process.exit();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user