mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Fix migrationMeta database version initial value, and move isDatabaseNew logic inside MigrationManager
This commit is contained in:
		
							parent
							
								
									5b09bd8242
								
							
						
					
					
						commit
						55164803b0
					
				| @ -171,9 +171,9 @@ class Database { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|       const migrationManager = new MigrationManager(this.sequelize, global.ConfigPath) |       const migrationManager = new MigrationManager(this.sequelize, this.isNew, global.ConfigPath) | ||||||
|       await migrationManager.init(packageJson.version) |       await migrationManager.init(packageJson.version) | ||||||
|       if (!this.isNew) await migrationManager.runMigrations() |       await migrationManager.runMigrations() | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       Logger.error(`[Database] Failed to run migrations`, error) |       Logger.error(`[Database] Failed to run migrations`, error) | ||||||
|       throw new Error('Database migration failed') |       throw new Error('Database migration failed') | ||||||
|  | |||||||
| @ -11,11 +11,13 @@ class MigrationManager { | |||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * @param {import('../Database').sequelize} sequelize |    * @param {import('../Database').sequelize} sequelize | ||||||
|  |    * @param {boolean} isDatabaseNew | ||||||
|    * @param {string} [configPath] |    * @param {string} [configPath] | ||||||
|    */ |    */ | ||||||
|   constructor(sequelize, configPath = global.configPath) { |   constructor(sequelize, isDatabaseNew, configPath = global.configPath) { | ||||||
|     if (!sequelize || !(sequelize instanceof Sequelize)) throw new Error('Sequelize instance is required for MigrationManager.') |     if (!sequelize || !(sequelize instanceof Sequelize)) throw new Error('Sequelize instance is required for MigrationManager.') | ||||||
|     this.sequelize = sequelize |     this.sequelize = sequelize | ||||||
|  |     this.isDatabaseNew = isDatabaseNew | ||||||
|     if (!configPath) throw new Error('Config path is required for MigrationManager.') |     if (!configPath) throw new Error('Config path is required for MigrationManager.') | ||||||
|     this.configPath = configPath |     this.configPath = configPath | ||||||
|     this.migrationsSourceDir = path.join(__dirname, '..', 'migrations') |     this.migrationsSourceDir = path.join(__dirname, '..', 'migrations') | ||||||
| @ -42,6 +44,7 @@ class MigrationManager { | |||||||
| 
 | 
 | ||||||
|     await this.fetchVersionsFromDatabase() |     await this.fetchVersionsFromDatabase() | ||||||
|     if (!this.maxVersion || !this.databaseVersion) throw new Error('Failed to fetch versions from the database.') |     if (!this.maxVersion || !this.databaseVersion) throw new Error('Failed to fetch versions from the database.') | ||||||
|  |     Logger.debug(`[MigrationManager] Database version: ${this.databaseVersion}, Max version: ${this.maxVersion}, Server version: ${this.serverVersion}`) | ||||||
| 
 | 
 | ||||||
|     if (semver.gt(this.serverVersion, this.maxVersion)) { |     if (semver.gt(this.serverVersion, this.maxVersion)) { | ||||||
|       try { |       try { | ||||||
| @ -63,6 +66,11 @@ class MigrationManager { | |||||||
|   async runMigrations() { |   async runMigrations() { | ||||||
|     if (!this.initialized) throw new Error('MigrationManager is not initialized. Call init() first.') |     if (!this.initialized) throw new Error('MigrationManager is not initialized. Call init() first.') | ||||||
| 
 | 
 | ||||||
|  |     if (this.isDatabaseNew) { | ||||||
|  |       Logger.info('[MigrationManager] Database is new. Skipping migrations.') | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const versionCompare = semver.compare(this.serverVersion, this.databaseVersion) |     const versionCompare = semver.compare(this.serverVersion, this.databaseVersion) | ||||||
|     if (versionCompare == 0) { |     if (versionCompare == 0) { | ||||||
|       Logger.info('[MigrationManager] Database is already up to date.') |       Logger.info('[MigrationManager] Database is already up to date.') | ||||||
| @ -180,7 +188,15 @@ class MigrationManager { | |||||||
| 
 | 
 | ||||||
|   async checkOrCreateMigrationsMetaTable() { |   async checkOrCreateMigrationsMetaTable() { | ||||||
|     const queryInterface = this.sequelize.getQueryInterface() |     const queryInterface = this.sequelize.getQueryInterface() | ||||||
|     if (!(await queryInterface.tableExists(MigrationManager.MIGRATIONS_META_TABLE))) { |     let migrationsMetaTableExists = await queryInterface.tableExists(MigrationManager.MIGRATIONS_META_TABLE) | ||||||
|  | 
 | ||||||
|  |     if (this.isDatabaseNew && migrationsMetaTableExists) { | ||||||
|  |       // This can happen if database was initialized with force: true
 | ||||||
|  |       await queryInterface.dropTable(MigrationManager.MIGRATIONS_META_TABLE) | ||||||
|  |       migrationsMetaTableExists = false | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!migrationsMetaTableExists) { | ||||||
|       await queryInterface.createTable(MigrationManager.MIGRATIONS_META_TABLE, { |       await queryInterface.createTable(MigrationManager.MIGRATIONS_META_TABLE, { | ||||||
|         key: { |         key: { | ||||||
|           type: DataTypes.STRING, |           type: DataTypes.STRING, | ||||||
| @ -192,9 +208,10 @@ class MigrationManager { | |||||||
|         } |         } | ||||||
|       }) |       }) | ||||||
|       await this.sequelize.query("INSERT INTO :migrationsMeta (key, value) VALUES ('version', :version), ('maxVersion', '0.0.0')", { |       await this.sequelize.query("INSERT INTO :migrationsMeta (key, value) VALUES ('version', :version), ('maxVersion', '0.0.0')", { | ||||||
|         replacements: { version: this.serverVersion, migrationsMeta: MigrationManager.MIGRATIONS_META_TABLE }, |         replacements: { version: this.isDatabaseNew ? this.serverVersion : '0.0.0', migrationsMeta: MigrationManager.MIGRATIONS_META_TABLE }, | ||||||
|         type: Sequelize.QueryTypes.INSERT |         type: Sequelize.QueryTypes.INSERT | ||||||
|       }) |       }) | ||||||
|  |       Logger.debug(`[MigrationManager] Created migrationsMeta table: "${MigrationManager.MIGRATIONS_META_TABLE}"`) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -219,6 +236,7 @@ class MigrationManager { | |||||||
|           await fs.copy(sourceFile, targetFile) // Asynchronously copy the files
 |           await fs.copy(sourceFile, targetFile) // Asynchronously copy the files
 | ||||||
|         }) |         }) | ||||||
|     ) |     ) | ||||||
|  |     Logger.debug(`[MigrationManager] Copied migrations to the config directory: "${this.migrationsDir}"`) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|  | |||||||
| @ -31,7 +31,7 @@ describe('MigrationManager', () => { | |||||||
|       down: sinon.stub() |       down: sinon.stub() | ||||||
|     } |     } | ||||||
|     sequelizeStub.getQueryInterface.returns({}) |     sequelizeStub.getQueryInterface.returns({}) | ||||||
|     migrationManager = new MigrationManager(sequelizeStub, configPath) |     migrationManager = new MigrationManager(sequelizeStub, false, configPath) | ||||||
|     migrationManager.fetchVersionsFromDatabase = sinon.stub().resolves() |     migrationManager.fetchVersionsFromDatabase = sinon.stub().resolves() | ||||||
|     migrationManager.copyMigrationsToConfigDir = sinon.stub().resolves() |     migrationManager.copyMigrationsToConfigDir = sinon.stub().resolves() | ||||||
|     migrationManager.updateMaxVersion = sinon.stub().resolves() |     migrationManager.updateMaxVersion = sinon.stub().resolves() | ||||||
| @ -131,6 +131,21 @@ describe('MigrationManager', () => { | |||||||
|       expect(loggerInfoStub.calledWith(sinon.match('Migrations successfully applied'))).to.be.true |       expect(loggerInfoStub.calledWith(sinon.match('Migrations successfully applied'))).to.be.true | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|  |     it('should log that migrations will be skipped if database is new', async () => { | ||||||
|  |       // Arrange
 | ||||||
|  |       migrationManager.isDatabaseNew = true | ||||||
|  |       migrationManager.initialized = true | ||||||
|  | 
 | ||||||
|  |       // Act
 | ||||||
|  |       await migrationManager.runMigrations() | ||||||
|  | 
 | ||||||
|  |       // Assert
 | ||||||
|  |       expect(loggerInfoStub.calledWith(sinon.match('Database is new. Skipping migrations.'))).to.be.true | ||||||
|  |       expect(migrationManager.initUmzug.called).to.be.false | ||||||
|  |       expect(umzugStub.up.called).to.be.false | ||||||
|  |       expect(umzugStub.down.called).to.be.false | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|     it('should log that no migrations are needed if serverVersion equals databaseVersion', async () => { |     it('should log that no migrations are needed if serverVersion equals databaseVersion', async () => { | ||||||
|       // Arrange
 |       // Arrange
 | ||||||
|       migrationManager.serverVersion = '1.2.0' |       migrationManager.serverVersion = '1.2.0' | ||||||
| @ -181,7 +196,7 @@ describe('MigrationManager', () => { | |||||||
|       // Create a migrationsMeta table and populate it with version and maxVersion
 |       // Create a migrationsMeta table and populate it with version and maxVersion
 | ||||||
|       await sequelize.query('CREATE TABLE migrationsMeta (key VARCHAR(255), value VARCHAR(255))') |       await sequelize.query('CREATE TABLE migrationsMeta (key VARCHAR(255), value VARCHAR(255))') | ||||||
|       await sequelize.query("INSERT INTO migrationsMeta (key, value) VALUES ('version', '1.1.0'), ('maxVersion', '1.1.0')") |       await sequelize.query("INSERT INTO migrationsMeta (key, value) VALUES ('version', '1.1.0'), ('maxVersion', '1.1.0')") | ||||||
|       const migrationManager = new MigrationManager(sequelize, configPath) |       const migrationManager = new MigrationManager(sequelize, false, configPath) | ||||||
|       migrationManager.checkOrCreateMigrationsMetaTable = sinon.stub().resolves() |       migrationManager.checkOrCreateMigrationsMetaTable = sinon.stub().resolves() | ||||||
| 
 | 
 | ||||||
|       // Act
 |       // Act
 | ||||||
| @ -195,7 +210,26 @@ describe('MigrationManager', () => { | |||||||
|     it('should create the migrationsMeta table if it does not exist and fetch versions from it', async () => { |     it('should create the migrationsMeta table if it does not exist and fetch versions from it', async () => { | ||||||
|       // Arrange
 |       // Arrange
 | ||||||
|       const sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false }) |       const sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false }) | ||||||
|       const migrationManager = new MigrationManager(sequelize, configPath) |       const migrationManager = new MigrationManager(sequelize, false, configPath) | ||||||
|  |       migrationManager.serverVersion = serverVersion | ||||||
|  | 
 | ||||||
|  |       // Act
 | ||||||
|  |       await migrationManager.fetchVersionsFromDatabase() | ||||||
|  | 
 | ||||||
|  |       // Assert
 | ||||||
|  |       const tableDescription = await sequelize.getQueryInterface().describeTable('migrationsMeta') | ||||||
|  |       expect(tableDescription).to.deep.equal({ | ||||||
|  |         key: { type: 'VARCHAR(255)', allowNull: false, defaultValue: undefined, primaryKey: false, unique: false }, | ||||||
|  |         value: { type: 'VARCHAR(255)', allowNull: false, defaultValue: undefined, primaryKey: false, unique: false } | ||||||
|  |       }) | ||||||
|  |       expect(migrationManager.maxVersion).to.equal('0.0.0') | ||||||
|  |       expect(migrationManager.databaseVersion).to.equal('0.0.0') | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     it('should create the migrationsMeta with databaseVersion=serverVersion if database is new', async () => { | ||||||
|  |       // Arrange
 | ||||||
|  |       const sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false }) | ||||||
|  |       const migrationManager = new MigrationManager(sequelize, true, configPath) | ||||||
|       migrationManager.serverVersion = serverVersion |       migrationManager.serverVersion = serverVersion | ||||||
| 
 | 
 | ||||||
|       // Act
 |       // Act
 | ||||||
| @ -211,11 +245,28 @@ describe('MigrationManager', () => { | |||||||
|       expect(migrationManager.databaseVersion).to.equal(serverVersion) |       expect(migrationManager.databaseVersion).to.equal(serverVersion) | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|  |     it('should re-create the migrationsMeta table if it existed and database is new (Database force=true)', async () => { | ||||||
|  |       // Arrange
 | ||||||
|  |       const sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false }) | ||||||
|  |       // Create a migrationsMeta table and populate it with version and maxVersion
 | ||||||
|  |       await sequelize.query('CREATE TABLE migrationsMeta (key VARCHAR(255), value VARCHAR(255))') | ||||||
|  |       await sequelize.query("INSERT INTO migrationsMeta (key, value) VALUES ('version', '1.1.0'), ('maxVersion', '1.1.0')") | ||||||
|  |       const migrationManager = new MigrationManager(sequelize, true, configPath) | ||||||
|  |       migrationManager.serverVersion = serverVersion | ||||||
|  | 
 | ||||||
|  |       // Act
 | ||||||
|  |       await migrationManager.fetchVersionsFromDatabase() | ||||||
|  | 
 | ||||||
|  |       // Assert
 | ||||||
|  |       expect(migrationManager.maxVersion).to.equal('0.0.0') | ||||||
|  |       expect(migrationManager.databaseVersion).to.equal(serverVersion) | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|     it('should throw an error if the database query fails', async () => { |     it('should throw an error if the database query fails', async () => { | ||||||
|       // Arrange
 |       // Arrange
 | ||||||
|       const sequelizeStub = sinon.createStubInstance(Sequelize) |       const sequelizeStub = sinon.createStubInstance(Sequelize) | ||||||
|       sequelizeStub.query.rejects(new Error('Database query failed')) |       sequelizeStub.query.rejects(new Error('Database query failed')) | ||||||
|       const migrationManager = new MigrationManager(sequelizeStub, configPath) |       const migrationManager = new MigrationManager(sequelizeStub, false, configPath) | ||||||
|       migrationManager.checkOrCreateMigrationsMetaTable = sinon.stub().resolves() |       migrationManager.checkOrCreateMigrationsMetaTable = sinon.stub().resolves() | ||||||
| 
 | 
 | ||||||
|       // Act
 |       // Act
 | ||||||
| @ -236,7 +287,7 @@ describe('MigrationManager', () => { | |||||||
|       // Create a migrationsMeta table and populate it with version and maxVersion
 |       // Create a migrationsMeta table and populate it with version and maxVersion
 | ||||||
|       await sequelize.query('CREATE TABLE migrationsMeta (key VARCHAR(255), value VARCHAR(255))') |       await sequelize.query('CREATE TABLE migrationsMeta (key VARCHAR(255), value VARCHAR(255))') | ||||||
|       await sequelize.query("INSERT INTO migrationsMeta (key, value) VALUES ('version', '1.1.0'), ('maxVersion', '1.1.0')") |       await sequelize.query("INSERT INTO migrationsMeta (key, value) VALUES ('version', '1.1.0'), ('maxVersion', '1.1.0')") | ||||||
|       const migrationManager = new MigrationManager(sequelize, configPath) |       const migrationManager = new MigrationManager(sequelize, false, configPath) | ||||||
|       migrationManager.serverVersion = '1.2.0' |       migrationManager.serverVersion = '1.2.0' | ||||||
| 
 | 
 | ||||||
|       // Act
 |       // Act
 | ||||||
| @ -253,7 +304,7 @@ describe('MigrationManager', () => { | |||||||
|   describe('extractVersionFromTag', () => { |   describe('extractVersionFromTag', () => { | ||||||
|     it('should return null if tag is not provided', () => { |     it('should return null if tag is not provided', () => { | ||||||
|       // Arrange
 |       // Arrange
 | ||||||
|       const migrationManager = new MigrationManager(sequelizeStub, configPath) |       const migrationManager = new MigrationManager(sequelizeStub, false, configPath) | ||||||
| 
 | 
 | ||||||
|       // Act
 |       // Act
 | ||||||
|       const result = migrationManager.extractVersionFromTag() |       const result = migrationManager.extractVersionFromTag() | ||||||
| @ -264,7 +315,7 @@ describe('MigrationManager', () => { | |||||||
| 
 | 
 | ||||||
|     it('should return null if tag does not match the version format', () => { |     it('should return null if tag does not match the version format', () => { | ||||||
|       // Arrange
 |       // Arrange
 | ||||||
|       const migrationManager = new MigrationManager(sequelizeStub, configPath) |       const migrationManager = new MigrationManager(sequelizeStub, false, configPath) | ||||||
|       const tag = 'invalid-tag' |       const tag = 'invalid-tag' | ||||||
| 
 | 
 | ||||||
|       // Act
 |       // Act
 | ||||||
| @ -276,7 +327,7 @@ describe('MigrationManager', () => { | |||||||
| 
 | 
 | ||||||
|     it('should extract the version from the tag', () => { |     it('should extract the version from the tag', () => { | ||||||
|       // Arrange
 |       // Arrange
 | ||||||
|       const migrationManager = new MigrationManager(sequelizeStub, configPath) |       const migrationManager = new MigrationManager(sequelizeStub, false, configPath) | ||||||
|       const tag = 'v1.2.3' |       const tag = 'v1.2.3' | ||||||
| 
 | 
 | ||||||
|       // Act
 |       // Act
 | ||||||
| @ -290,7 +341,7 @@ describe('MigrationManager', () => { | |||||||
|   describe('copyMigrationsToConfigDir', () => { |   describe('copyMigrationsToConfigDir', () => { | ||||||
|     it('should copy migrations to the config directory', async () => { |     it('should copy migrations to the config directory', async () => { | ||||||
|       // Arrange
 |       // Arrange
 | ||||||
|       const migrationManager = new MigrationManager(sequelizeStub, configPath) |       const migrationManager = new MigrationManager(sequelizeStub, false, configPath) | ||||||
|       migrationManager.migrationsDir = path.join(configPath, 'migrations') |       migrationManager.migrationsDir = path.join(configPath, 'migrations') | ||||||
|       const migrationsSourceDir = path.join(__dirname, '..', '..', '..', 'server', 'migrations') |       const migrationsSourceDir = path.join(__dirname, '..', '..', '..', 'server', 'migrations') | ||||||
|       const targetDir = migrationManager.migrationsDir |       const targetDir = migrationManager.migrationsDir | ||||||
| @ -313,7 +364,7 @@ describe('MigrationManager', () => { | |||||||
| 
 | 
 | ||||||
|     it('should throw an error if copying the migrations fails', async () => { |     it('should throw an error if copying the migrations fails', async () => { | ||||||
|       // Arrange
 |       // Arrange
 | ||||||
|       const migrationManager = new MigrationManager(sequelizeStub, configPath) |       const migrationManager = new MigrationManager(sequelizeStub, false, configPath) | ||||||
|       migrationManager.migrationsDir = path.join(configPath, 'migrations') |       migrationManager.migrationsDir = path.join(configPath, 'migrations') | ||||||
|       const migrationsSourceDir = path.join(__dirname, '..', '..', '..', 'server', 'migrations') |       const migrationsSourceDir = path.join(__dirname, '..', '..', '..', 'server', 'migrations') | ||||||
|       const targetDir = migrationManager.migrationsDir |       const targetDir = migrationManager.migrationsDir | ||||||
| @ -484,7 +535,7 @@ describe('MigrationManager', () => { | |||||||
|       const readdirStub = sinon.stub(fs, 'readdir').resolves(['v1.0.0-migration.js', 'v1.10.0-migration.js', 'v1.2.0-migration.js', 'v1.1.0-migration.js']) |       const readdirStub = sinon.stub(fs, 'readdir').resolves(['v1.0.0-migration.js', 'v1.10.0-migration.js', 'v1.2.0-migration.js', 'v1.1.0-migration.js']) | ||||||
|       const readFileSyncStub = sinon.stub(fs, 'readFileSync').returns('module.exports = { up: () => {}, down: () => {} }') |       const readFileSyncStub = sinon.stub(fs, 'readFileSync').returns('module.exports = { up: () => {}, down: () => {} }') | ||||||
|       const umzugStorage = memoryStorage() |       const umzugStorage = memoryStorage() | ||||||
|       migrationManager = new MigrationManager(sequelizeStub, configPath) |       migrationManager = new MigrationManager(sequelizeStub, false, configPath) | ||||||
|       migrationManager.migrationsDir = path.join(configPath, 'migrations') |       migrationManager.migrationsDir = path.join(configPath, 'migrations') | ||||||
|       const resolvedMigrationNames = ['v1.0.0-migration.js', 'v1.1.0-migration.js', 'v1.2.0-migration.js', 'v1.10.0-migration.js'] |       const resolvedMigrationNames = ['v1.0.0-migration.js', 'v1.1.0-migration.js', 'v1.2.0-migration.js', 'v1.10.0-migration.js'] | ||||||
|       const resolvedMigrationPaths = resolvedMigrationNames.map((name) => path.resolve(path.join(migrationManager.migrationsDir, name))) |       const resolvedMigrationPaths = resolvedMigrationNames.map((name) => path.resolve(path.join(migrationManager.migrationsDir, name))) | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user