diff --git a/backend/migrations/20260413000001-ensure-unlimited-text-fields.js b/backend/migrations/20260413000001-ensure-unlimited-text-fields.js new file mode 100644 index 0000000..8d46aa6 --- /dev/null +++ b/backend/migrations/20260413000001-ensure-unlimited-text-fields.js @@ -0,0 +1,47 @@ +'use strict'; + +module.exports = { + async up(queryInterface, Sequelize) { + // Change task name from VARCHAR(255) to TEXT to prevent truncation + await queryInterface.changeColumn('tasks', 'name', { + type: Sequelize.TEXT, + allowNull: false, + }); + + // Change inbox_items cleaned_content from VARCHAR(255) to TEXT + await queryInterface.changeColumn('inbox_items', 'cleaned_content', { + type: Sequelize.TEXT, + allowNull: true, + comment: 'Content with tags and project references removed', + }); + + // Change inbox_items title from VARCHAR(255) to TEXT + await queryInterface.changeColumn('inbox_items', 'title', { + type: Sequelize.TEXT, + allowNull: true, + comment: + 'Optional title field for inbox items, auto-generated for long content', + }); + }, + + async down(queryInterface, Sequelize) { + // Revert back to VARCHAR(255) + await queryInterface.changeColumn('tasks', 'name', { + type: Sequelize.STRING, + allowNull: false, + }); + + await queryInterface.changeColumn('inbox_items', 'cleaned_content', { + type: Sequelize.STRING, + allowNull: true, + comment: 'Content with tags and project references removed', + }); + + await queryInterface.changeColumn('inbox_items', 'title', { + type: Sequelize.STRING, + allowNull: true, + comment: + 'Optional title field for inbox items, auto-generated for long content', + }); + }, +}; diff --git a/backend/models/inbox_item.js b/backend/models/inbox_item.js index 16adeed..28a589e 100644 --- a/backend/models/inbox_item.js +++ b/backend/models/inbox_item.js @@ -21,7 +21,7 @@ module.exports = (sequelize) => { allowNull: false, }, title: { - type: DataTypes.STRING, + type: DataTypes.TEXT, allowNull: true, }, status: { @@ -58,7 +58,7 @@ module.exports = (sequelize) => { allowNull: true, }, cleaned_content: { - type: DataTypes.STRING, + type: DataTypes.TEXT, allowNull: true, }, }, diff --git a/backend/models/task.js b/backend/models/task.js index d13b394..cee0362 100644 --- a/backend/models/task.js +++ b/backend/models/task.js @@ -17,7 +17,7 @@ module.exports = (sequelize) => { defaultValue: uid, }, name: { - type: DataTypes.STRING, + type: DataTypes.TEXT, allowNull: false, }, due_date: { diff --git a/backend/tests/integration/tasks.test.js b/backend/tests/integration/tasks.test.js index b85465b..62341f1 100644 --- a/backend/tests/integration/tasks.test.js +++ b/backend/tests/integration/tasks.test.js @@ -68,6 +68,27 @@ describe('Tasks Routes', () => { // Restore original console.error console.error = originalConsoleError; }); + + it('should not truncate long task names', async () => { + const longTaskName = + 'This task has a long name, very long name, with lots of stuff'; + const taskData = { + name: longTaskName, + priority: 'low', + status: 'not_started', + }; + + const response = await agent.post('/api/task').send(taskData); + + expect(response.status).toBe(201); + expect(response.body.name).toBe(longTaskName); + expect(response.body.name.length).toBe(longTaskName.length); + + const retrievedTask = await Task.findOne({ + where: { id: response.body.id }, + }); + expect(retrievedTask.name).toBe(longTaskName); + }); }); describe('GET /api/tasks', () => { diff --git a/backend/tests/unit/services/inboxProcessingService.test.js b/backend/tests/unit/services/inboxProcessingService.test.js index 2bf32e0..93a8484 100644 --- a/backend/tests/unit/services/inboxProcessingService.test.js +++ b/backend/tests/unit/services/inboxProcessingService.test.js @@ -56,6 +56,18 @@ describe('inboxProcessingService', () => { suggested_reason: null, }); }); + + it('should not truncate long cleaned_content', () => { + const longText = + 'This task has a long name, very long name, with lots of stuff'; + const content = `${longText} #tag1 +Project`; + const result = processInboxItem(content); + + expect(result.parsed_tags).toEqual(['tag1']); + expect(result.parsed_projects).toEqual(['Project']); + expect(result.cleaned_content).toBe(longText); + expect(result.cleaned_content.length).toBe(longText.length); + }); }); describe('containsUrl', () => {