mirror of
https://github.com/hexastack/hexabot
synced 2025-06-03 11:06:34 +00:00
fix: migration and db version logic
This commit is contained in:
parent
c94ec95599
commit
a1765b647b
@ -14,22 +14,17 @@ import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { LoggerModule } from '@/logger/logger.module';
|
||||
|
||||
import { MigrationCommand } from './migration.command';
|
||||
import { Migration, MigrationSchema } from './migration.schema';
|
||||
import { MigrationModel } from './migration.schema';
|
||||
import { MigrationService } from './migration.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MongooseModule.forFeature([
|
||||
{ name: Migration.name, schema: MigrationSchema },
|
||||
]),
|
||||
LoggerModule,
|
||||
],
|
||||
imports: [MongooseModule.forFeature([MigrationModel]), LoggerModule],
|
||||
providers: [
|
||||
MigrationService,
|
||||
MigrationCommand,
|
||||
{
|
||||
provide: 'MONGO_MIGRATION_DIR',
|
||||
useValue: join(process.cwd(), 'src', 'migration', 'migrations'),
|
||||
useValue: join(__dirname, 'migrations'),
|
||||
},
|
||||
],
|
||||
exports: [MigrationService],
|
||||
|
@ -6,8 +6,10 @@
|
||||
* 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 { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||
import { Document, Model } from 'mongoose';
|
||||
import { ModelDefinition, Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||
|
||||
import { LifecycleHookManager } from '@/utils/generics/lifecycle-hook-manager';
|
||||
import { THydratedDocument } from '@/utils/types/filter.types';
|
||||
|
||||
import { MigrationAction } from './types';
|
||||
|
||||
@ -17,11 +19,14 @@ export class Migration {
|
||||
name: string;
|
||||
|
||||
@Prop({ type: String, required: true, enum: MigrationAction })
|
||||
status: string;
|
||||
status: MigrationAction;
|
||||
}
|
||||
|
||||
export const MigrationSchema = SchemaFactory.createForClass(Migration);
|
||||
export const MigrationModel: ModelDefinition = LifecycleHookManager.attach({
|
||||
name: Migration.name,
|
||||
schema: SchemaFactory.createForClass(Migration),
|
||||
});
|
||||
|
||||
export type MigrationDocument = Migration & Document;
|
||||
export default MigrationModel.schema;
|
||||
|
||||
export type MigrationModel = Model<MigrationDocument>;
|
||||
export type MigrationDocument = THydratedDocument<Migration>;
|
||||
|
@ -13,7 +13,7 @@ import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { kebabCase } from 'lodash';
|
||||
import mongoose from 'mongoose';
|
||||
import mongoose, { Model } from 'mongoose';
|
||||
import leanDefaults from 'mongoose-lean-defaults';
|
||||
import leanGetters from 'mongoose-lean-getters';
|
||||
import leanVirtuals from 'mongoose-lean-virtuals';
|
||||
@ -23,11 +23,7 @@ import { LoggerService } from '@/logger/logger.service';
|
||||
import { MetadataService } from '@/setting/services/metadata.service';
|
||||
import idPlugin from '@/utils/schema-plugin/id.plugin';
|
||||
|
||||
import {
|
||||
Migration,
|
||||
MigrationDocument,
|
||||
MigrationModel,
|
||||
} from './migration.schema';
|
||||
import { Migration, MigrationDocument } from './migration.schema';
|
||||
import {
|
||||
MigrationAction,
|
||||
MigrationRunParams,
|
||||
@ -41,7 +37,7 @@ export class MigrationService implements OnApplicationBootstrap {
|
||||
private readonly logger: LoggerService,
|
||||
private readonly metadataService: MetadataService,
|
||||
@InjectModel(Migration.name)
|
||||
private readonly migrationModel: MigrationModel,
|
||||
private readonly migrationModel: Model<Migration>,
|
||||
) {
|
||||
this.validateMigrationPath();
|
||||
}
|
||||
@ -55,8 +51,8 @@ export class MigrationService implements OnApplicationBootstrap {
|
||||
const isCLI = Boolean(process.env.HEXABOT_CLI);
|
||||
if (!isCLI && config.mongo.autoMigrate) {
|
||||
this.logger.log('Executing migrations ...');
|
||||
const { value: version = '2.1.9' } =
|
||||
await this.metadataService.getMetadata('db-version');
|
||||
const metadata = await this.metadataService.getMetadata('db-version');
|
||||
const version = metadata ? metadata.value : 'v2.1.9';
|
||||
await this.run({
|
||||
action: MigrationAction.UP,
|
||||
version,
|
||||
@ -89,8 +85,7 @@ export class MigrationService implements OnApplicationBootstrap {
|
||||
// check if file already exists
|
||||
const files = await this.getDirFiles();
|
||||
const exist = files.some((file) => {
|
||||
const [, ...actualFileName] = file.split('-');
|
||||
const migrationName = actualFileName.join('-');
|
||||
const migrationName = this.getMigrationName(file);
|
||||
return migrationName === fileName;
|
||||
});
|
||||
|
||||
@ -152,14 +147,19 @@ module.exports = {
|
||||
if (!name) {
|
||||
if (isAutoMigrate) {
|
||||
const newVersion = await this.runFromVersion(action, version);
|
||||
await this.metadataService.setMetadata('db-version', newVersion);
|
||||
|
||||
await this.metadataService.findOrCreate({
|
||||
name: 'db-version',
|
||||
value: newVersion,
|
||||
});
|
||||
} else {
|
||||
await this.runAll(action);
|
||||
this.exit();
|
||||
}
|
||||
} else {
|
||||
await this.runOne({ action, name });
|
||||
this.exit();
|
||||
}
|
||||
this.exit();
|
||||
}
|
||||
|
||||
private async runOne({ name, action }: MigrationRunParams) {
|
||||
@ -218,7 +218,7 @@ module.exports = {
|
||||
private async runFromVersion(action: MigrationAction, version: string) {
|
||||
const files = await this.getDirFiles();
|
||||
const migrationFiles = files
|
||||
.filter((fileName) => fileName.includes('migration'))
|
||||
.filter((fileName) => fileName.endsWith('.migration.js'))
|
||||
.map((fileName) => {
|
||||
const [migrationFileName] = fileName.split('.');
|
||||
const [, , ...migrationVersion] = migrationFileName.split('-');
|
||||
@ -273,20 +273,26 @@ module.exports = {
|
||||
return { exist, migrationDocument };
|
||||
}
|
||||
|
||||
private async getMigrationFiles() {
|
||||
async getMigrationFiles() {
|
||||
const files = await this.getDirFiles();
|
||||
return files.filter((file) => /\.migration\.(js|ts)/.test(file));
|
||||
}
|
||||
|
||||
private async findMigrationFileByName(name: string): Promise<string | null> {
|
||||
private getMigrationName(filename: string) {
|
||||
const [, ...migrationNameParts] = filename.split('-');
|
||||
const migrationName = migrationNameParts.join('-');
|
||||
|
||||
return migrationName;
|
||||
}
|
||||
|
||||
async findMigrationFileByName(name: string): Promise<string | null> {
|
||||
const files = await this.getMigrationFiles();
|
||||
return (
|
||||
files.find((file) => {
|
||||
const [, ...migrationNameParts] = file.split('-');
|
||||
const migrationName = migrationNameParts
|
||||
.join('-')
|
||||
.replace(/\.migration\.(js|ts)/, '');
|
||||
|
||||
const migrationName = this.getMigrationName(file).replace(
|
||||
/\.migration\.(js|ts)/,
|
||||
'',
|
||||
);
|
||||
return migrationName === kebabCase(name);
|
||||
}) || null
|
||||
);
|
||||
@ -318,7 +324,7 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
|
||||
private async updateStatus({
|
||||
async updateStatus({
|
||||
name,
|
||||
action,
|
||||
migrationDocument,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Hexastack. All rights reserved.
|
||||
* 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.
|
||||
@ -19,6 +19,20 @@ export class MetadataService {
|
||||
private readonly metadataModel: Model<Metadata>,
|
||||
) {}
|
||||
|
||||
async createMetadata(dto: Partial<Metadata>) {
|
||||
return await this.metadataModel.create(dto);
|
||||
}
|
||||
|
||||
async findOrCreate(dto: Partial<Metadata>) {
|
||||
const metadata = await this.metadataModel.findOne({ name: dto.name });
|
||||
|
||||
if (metadata) {
|
||||
await this.setMetadata(dto.name, dto.value);
|
||||
} else {
|
||||
await this.createMetadata(dto);
|
||||
}
|
||||
}
|
||||
|
||||
async getMetadata(name: string) {
|
||||
return await this.metadataModel.findOne({ name });
|
||||
}
|
||||
|
23
api/src/utils/test/fixtures/metadata.ts
vendored
Normal file
23
api/src/utils/test/fixtures/metadata.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 { Metadata, MetadataModel } from '@/setting/schemas/metadata.schema';
|
||||
|
||||
const metadataFixtures: Metadata[] = [
|
||||
{
|
||||
name: 'app-version',
|
||||
value: '2.2.0',
|
||||
},
|
||||
];
|
||||
|
||||
export const installMetadataFixtures = async () => {
|
||||
const Metadata = mongoose.model(MetadataModel.name, MetadataModel.schema);
|
||||
return await Metadata.insertMany(metadataFixtures);
|
||||
};
|
28
api/src/utils/test/fixtures/migration.ts
vendored
Normal file
28
api/src/utils/test/fixtures/migration.ts
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 { Migration, MigrationModel } from '@/migration/migration.schema';
|
||||
import { MigrationAction } from '@/migration/types';
|
||||
|
||||
const migrationFixtures: Migration[] = [
|
||||
{
|
||||
name: 'v2.1.2',
|
||||
status: MigrationAction.UP,
|
||||
},
|
||||
{
|
||||
name: 'v2.1.1',
|
||||
status: MigrationAction.DOWN,
|
||||
},
|
||||
];
|
||||
|
||||
export const installMigrationFixtures = async () => {
|
||||
const Migration = mongoose.model(MigrationModel.name, MigrationModel.schema);
|
||||
return await Migration.insertMany(migrationFixtures);
|
||||
};
|
Loading…
Reference in New Issue
Block a user