* fix: replace 6-word limit with 150-character limit for project names Replaces the word-based validation with character-based validation as originally requested in #971. The 6-word limit was causing issues with small words and separators being counted equally, and didn't match the original requirement for a character limit. Changes: - Backend: Replace wordCount validator with len validator (1-150 chars) - Frontend: Replace word count validation with character length check - UI already has line-clamp-3 for display truncation Fixes #998 * fix: make password_digest migration compatible with all schema versions Fixes a critical bug where the make-password-optional migration would silently fail when upgrading from v1.0.0 or running on fresh v1.1.0-dev installations. The migration was trying to SELECT columns (ai_provider, openai_api_key, ollama_base_url, ollama_model) that don't exist in the users table at that point in the migration chain, causing the INSERT...SELECT to fail and leaving password_digest as NOT NULL. This prevented OIDC auto-provisioning from creating new users without passwords. The fix dynamically detects which columns exist in the users table using PRAGMA table_info and only selects columns that are guaranteed to exist. Missing columns (AI-related fields) will receive their default values from the new table schema. Changes: - Added dynamic column detection using PRAGMA table_info - Only SELECT columns that exist in the current users table - AI columns get default values if they don't exist yet - Applied same fix to both up and down migrations - Properly handle password/password_digest column name migration Fixes #1075
273 lines
9.9 KiB
JavaScript
273 lines
9.9 KiB
JavaScript
'use strict';
|
|
|
|
module.exports = {
|
|
async up(queryInterface, Sequelize) {
|
|
await queryInterface.sequelize.query('PRAGMA foreign_keys = OFF;');
|
|
|
|
await queryInterface.sequelize.query('DROP TABLE IF EXISTS users_new;');
|
|
|
|
await queryInterface.sequelize.query(`
|
|
CREATE TABLE users_new (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
uid VARCHAR(255) NOT NULL UNIQUE,
|
|
name VARCHAR(255),
|
|
surname VARCHAR(255),
|
|
email VARCHAR(255) NOT NULL UNIQUE,
|
|
password_digest VARCHAR(255),
|
|
appearance VARCHAR(255) NOT NULL DEFAULT 'light',
|
|
language VARCHAR(255) NOT NULL DEFAULT 'en',
|
|
timezone VARCHAR(255) NOT NULL DEFAULT 'UTC',
|
|
first_day_of_week INTEGER NOT NULL DEFAULT 1,
|
|
avatar_image VARCHAR(255),
|
|
telegram_bot_token VARCHAR(255),
|
|
telegram_chat_id VARCHAR(255),
|
|
task_summary_enabled TINYINT(1) NOT NULL DEFAULT 0,
|
|
task_summary_frequency VARCHAR(255) DEFAULT 'daily',
|
|
task_summary_last_run DATETIME,
|
|
task_summary_next_run DATETIME,
|
|
telegram_allowed_users TEXT,
|
|
task_intelligence_enabled TINYINT(1) NOT NULL DEFAULT 1,
|
|
auto_suggest_next_actions_enabled TINYINT(1) NOT NULL DEFAULT 0,
|
|
pomodoro_enabled TINYINT(1) NOT NULL DEFAULT 1,
|
|
productivity_assistant_enabled TINYINT(1) NOT NULL DEFAULT 1,
|
|
next_task_suggestion_enabled TINYINT(1) NOT NULL DEFAULT 1,
|
|
today_settings JSON,
|
|
sidebar_settings JSON,
|
|
ui_settings JSON,
|
|
notification_preferences JSON,
|
|
keyboard_shortcuts JSON,
|
|
email_verified TINYINT(1) NOT NULL DEFAULT 1,
|
|
email_verification_token VARCHAR(255),
|
|
email_verification_token_expires_at DATETIME,
|
|
created_at DATETIME NOT NULL,
|
|
updated_at DATETIME NOT NULL,
|
|
ai_provider VARCHAR(255) NOT NULL DEFAULT 'openai',
|
|
openai_api_key VARCHAR(255),
|
|
ollama_base_url VARCHAR(255) DEFAULT 'http://localhost:11434',
|
|
ollama_model VARCHAR(255) DEFAULT 'llama3'
|
|
);
|
|
`);
|
|
|
|
const [columns] = await queryInterface.sequelize.query(
|
|
'PRAGMA table_info(users);'
|
|
);
|
|
const hasPasswordDigest = columns.some(
|
|
(col) => col.name === 'password_digest'
|
|
);
|
|
const hasPassword = columns.some((col) => col.name === 'password');
|
|
|
|
const passwordColumn = hasPasswordDigest
|
|
? 'password_digest'
|
|
: hasPassword
|
|
? 'password'
|
|
: null;
|
|
|
|
if (!passwordColumn) {
|
|
throw new Error(
|
|
'Neither password nor password_digest column found in users table'
|
|
);
|
|
}
|
|
|
|
const existingColumns = new Set(columns.map((col) => col.name));
|
|
|
|
const baseColumns = [
|
|
'id',
|
|
'uid',
|
|
'name',
|
|
'surname',
|
|
'email',
|
|
'appearance',
|
|
'language',
|
|
'timezone',
|
|
'first_day_of_week',
|
|
'avatar_image',
|
|
'telegram_bot_token',
|
|
'telegram_chat_id',
|
|
'task_summary_enabled',
|
|
'task_summary_frequency',
|
|
'task_summary_last_run',
|
|
'task_summary_next_run',
|
|
'telegram_allowed_users',
|
|
'task_intelligence_enabled',
|
|
'auto_suggest_next_actions_enabled',
|
|
'pomodoro_enabled',
|
|
'productivity_assistant_enabled',
|
|
'next_task_suggestion_enabled',
|
|
'today_settings',
|
|
'sidebar_settings',
|
|
'ui_settings',
|
|
'notification_preferences',
|
|
'keyboard_shortcuts',
|
|
'email_verified',
|
|
'email_verification_token',
|
|
'email_verification_token_expires_at',
|
|
'created_at',
|
|
'updated_at',
|
|
];
|
|
|
|
const aiColumns = [
|
|
'ai_provider',
|
|
'openai_api_key',
|
|
'ollama_base_url',
|
|
'ollama_model',
|
|
];
|
|
|
|
const columnsToSelect = baseColumns.filter((col) =>
|
|
existingColumns.has(col)
|
|
);
|
|
|
|
const aiColumnsToSelect = aiColumns.filter((col) =>
|
|
existingColumns.has(col)
|
|
);
|
|
|
|
const columnsWithoutPassword = columnsToSelect.filter(
|
|
(col) => col !== passwordColumn && col !== 'password_digest'
|
|
);
|
|
|
|
const insertColumns = [
|
|
...columnsWithoutPassword,
|
|
'password_digest',
|
|
...aiColumnsToSelect,
|
|
];
|
|
const selectColumns = [
|
|
...columnsWithoutPassword,
|
|
`${passwordColumn} as password_digest`,
|
|
...aiColumnsToSelect,
|
|
];
|
|
|
|
const insertColumnsStr = insertColumns.join(', ');
|
|
const selectColumnsStr = selectColumns.join(', ');
|
|
|
|
await queryInterface.sequelize.query(`
|
|
INSERT INTO users_new (${insertColumnsStr})
|
|
SELECT ${selectColumnsStr}
|
|
FROM users;
|
|
`);
|
|
|
|
await queryInterface.sequelize.query('DROP TABLE users;');
|
|
|
|
await queryInterface.sequelize.query(
|
|
'ALTER TABLE users_new RENAME TO users;'
|
|
);
|
|
|
|
await queryInterface.sequelize.query('PRAGMA foreign_keys = ON;');
|
|
},
|
|
|
|
async down(queryInterface, Sequelize) {
|
|
await queryInterface.sequelize.query('PRAGMA foreign_keys = OFF;');
|
|
|
|
await queryInterface.sequelize.query('DROP TABLE IF EXISTS users_new;');
|
|
|
|
await queryInterface.sequelize.query(`
|
|
CREATE TABLE users_new (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
uid VARCHAR(255) NOT NULL UNIQUE,
|
|
name VARCHAR(255),
|
|
surname VARCHAR(255),
|
|
email VARCHAR(255) NOT NULL UNIQUE,
|
|
password_digest VARCHAR(255) NOT NULL,
|
|
appearance VARCHAR(255) NOT NULL DEFAULT 'light',
|
|
language VARCHAR(255) NOT NULL DEFAULT 'en',
|
|
timezone VARCHAR(255) NOT NULL DEFAULT 'UTC',
|
|
first_day_of_week INTEGER NOT NULL DEFAULT 1,
|
|
avatar_image VARCHAR(255),
|
|
telegram_bot_token VARCHAR(255),
|
|
telegram_chat_id VARCHAR(255),
|
|
task_summary_enabled TINYINT(1) NOT NULL DEFAULT 0,
|
|
task_summary_frequency VARCHAR(255) DEFAULT 'daily',
|
|
task_summary_last_run DATETIME,
|
|
task_summary_next_run DATETIME,
|
|
telegram_allowed_users TEXT,
|
|
task_intelligence_enabled TINYINT(1) NOT NULL DEFAULT 1,
|
|
auto_suggest_next_actions_enabled TINYINT(1) NOT NULL DEFAULT 0,
|
|
pomodoro_enabled TINYINT(1) NOT NULL DEFAULT 1,
|
|
productivity_assistant_enabled TINYINT(1) NOT NULL DEFAULT 1,
|
|
next_task_suggestion_enabled TINYINT(1) NOT NULL DEFAULT 1,
|
|
today_settings JSON,
|
|
sidebar_settings JSON,
|
|
ui_settings JSON,
|
|
notification_preferences JSON,
|
|
keyboard_shortcuts JSON,
|
|
email_verified TINYINT(1) NOT NULL DEFAULT 1,
|
|
email_verification_token VARCHAR(255),
|
|
email_verification_token_expires_at DATETIME,
|
|
created_at DATETIME NOT NULL,
|
|
updated_at DATETIME NOT NULL,
|
|
ai_provider VARCHAR(255) NOT NULL DEFAULT 'openai',
|
|
openai_api_key VARCHAR(255),
|
|
ollama_base_url VARCHAR(255) DEFAULT 'http://localhost:11434',
|
|
ollama_model VARCHAR(255) DEFAULT 'llama3'
|
|
);
|
|
`);
|
|
|
|
const [columns] = await queryInterface.sequelize.query(
|
|
'PRAGMA table_info(users);'
|
|
);
|
|
const existingColumns = new Set(columns.map((col) => col.name));
|
|
|
|
const baseColumns = [
|
|
'id',
|
|
'uid',
|
|
'name',
|
|
'surname',
|
|
'email',
|
|
'password_digest',
|
|
'appearance',
|
|
'language',
|
|
'timezone',
|
|
'first_day_of_week',
|
|
'avatar_image',
|
|
'telegram_bot_token',
|
|
'telegram_chat_id',
|
|
'task_summary_enabled',
|
|
'task_summary_frequency',
|
|
'task_summary_last_run',
|
|
'task_summary_next_run',
|
|
'telegram_allowed_users',
|
|
'task_intelligence_enabled',
|
|
'auto_suggest_next_actions_enabled',
|
|
'pomodoro_enabled',
|
|
'productivity_assistant_enabled',
|
|
'next_task_suggestion_enabled',
|
|
'today_settings',
|
|
'sidebar_settings',
|
|
'ui_settings',
|
|
'notification_preferences',
|
|
'keyboard_shortcuts',
|
|
'email_verified',
|
|
'email_verification_token',
|
|
'email_verification_token_expires_at',
|
|
'created_at',
|
|
'updated_at',
|
|
];
|
|
|
|
const aiColumns = [
|
|
'ai_provider',
|
|
'openai_api_key',
|
|
'ollama_base_url',
|
|
'ollama_model',
|
|
];
|
|
|
|
const columnsToSelect = [
|
|
...baseColumns.filter((col) => existingColumns.has(col)),
|
|
...aiColumns.filter((col) => existingColumns.has(col)),
|
|
];
|
|
|
|
const insertColumnsStr = columnsToSelect.join(', ');
|
|
const selectColumnsStr = columnsToSelect.join(', ');
|
|
|
|
await queryInterface.sequelize.query(`
|
|
INSERT INTO users_new (${insertColumnsStr})
|
|
SELECT ${selectColumnsStr}
|
|
FROM users;
|
|
`);
|
|
|
|
await queryInterface.sequelize.query('DROP TABLE users;');
|
|
|
|
await queryInterface.sequelize.query(
|
|
'ALTER TABLE users_new RENAME TO users;'
|
|
);
|
|
|
|
await queryInterface.sequelize.query('PRAGMA foreign_keys = ON;');
|
|
},
|
|
};
|