diff --git a/api/src/migration/migrations/1748492346868-v-2-2-9.migration.ts b/api/src/migration/migrations/1748492346868-v-2-2-9.migration.ts new file mode 100644 index 00000000..ad3190a7 --- /dev/null +++ b/api/src/migration/migrations/1748492346868-v-2-2-9.migration.ts @@ -0,0 +1,135 @@ +/* + * Copyright © 2025 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import mongoose from 'mongoose'; + +import blockSchema, { Block } from '@/chat/schemas/block.schema'; +import roleSchema, { Role } from '@/user/schemas/role.schema'; +import userSchema, { User } from '@/user/schemas/user.schema'; + +import { MigrationServices } from '../types'; + +/** + * @returns The admin user or null + */ +const getAdminUser = async () => { + const RoleModel = mongoose.model(Role.name, roleSchema); + const UserModel = mongoose.model(User.name, userSchema); + + const adminRole = await RoleModel.findOne({ name: 'admin' }); + const user = await UserModel.findOne({ roles: [adminRole!._id] }).sort({ + createdAt: 'asc', + }); + + return user!; +}; + +const migrateBlockOptionsContentLimit = async (services: MigrationServices) => { + const BlockModel = mongoose.model(Block.name, blockSchema).collection; + + const adminUser = await getAdminUser(); + + if (!adminUser) { + services.logger.warn('Unable to process block, no admin user found'); + return; + } + + try { + await BlockModel.updateMany( + { 'options.content.limit': { $exists: true } }, + [ + { + $set: { + 'options.content.limit': { $toInt: '$options.content.limit' }, + }, + }, + ], + ); + } catch (error) { + services.logger.error(`Failed to update limit : ${error.message}`); + + throw error instanceof Error ? error : new Error(error); + } +}; + +const migrateBlockOptionsContentButtonsUrl = async ( + services: MigrationServices, +) => { + const BlockModel = mongoose.model(Block.name, blockSchema).collection; + + try { + await BlockModel.updateMany( + { 'options.content.buttons.url': false }, + { + $set: { + 'options.content.buttons.$[].url': '', + }, + }, + ); + } catch (error) { + services.logger.error(`Failed to update button url : ${error.message}`); + + throw error instanceof Error ? error : new Error(error); + } +}; + +const migrateBlockOptionsFallback = async (services: MigrationServices) => { + const BlockModel = mongoose.model(Block.name, blockSchema).collection; + + try { + await BlockModel.updateMany( + { 'options.fallback.max_attempts': { $exists: true, $type: 'string' } }, + [ + { + $set: { + 'options.fallback.max_attempts': { + $toInt: '$options.fallback.max_attempts', + }, + }, + }, + ], + ); + } catch (error) { + services.logger.error(`Failed to update max_attempts : ${error.message}`); + throw error instanceof Error ? error : new Error(error); + } + + try { + await BlockModel.updateMany({ 'options.fallback.message': { $size: 0 } }, [ + { + $set: { + 'options.fallback.max_attempts': 0, + 'options.fallback.active': false, + }, + }, + ]); + } catch (error) { + services.logger.error( + `Failed to update max_attempts, active : ${error.message}`, + ); + throw error instanceof Error ? error : new Error(error); + } +}; + +module.exports = { + async up(services: MigrationServices) { + try { + await migrateBlockOptionsContentLimit(services); + await migrateBlockOptionsContentButtonsUrl(services); + await migrateBlockOptionsFallback(services); + + return true; + } catch (error) { + services.logger.error(`Migration failed : ${error.message}`); + throw error instanceof Error ? error : new Error(error); + } + }, + async down(_services: MigrationServices) { + return true; + }, +};